Repository: google/TensorNetwork

Issue:
TensorNetwork.kron does not work correctly
TensorNetwork.kron is not what kron works in the usual way. 
kron(A,B) should be equivalent to ncon([A,B],[[-1,-3],[-2,-4]]) but it does not work out this way. 


Discussion:
Can you give a repo code? You're right those two should be equal.
I am not sure whether I understand your question. Are you asking for the source code of kron in the repository or are you asking for an instance that the kron method goes wrong?
> asking for an instance that the kron method goes wrong?

That one
The main reason I ask is to see how you use kron.
import numpy as np
import TensorNetwork as tn
from TensorNetwork.linalg.operations import ncon
X=tn.Tensor(np.array([[0.0,1.0],[1.0,0.0]]))
Id=tn.Tensor(np.eye(3))

XI_kron=tn.kron(X,Id)
XI_ncon=ncon([X,Id],[[-1,-3],[-2,-4]])

print(XI_kron.array.shape)  #This one has shape (2,2,3,3)
print(XI_ncon.array.shape)  #This one has shape (2,3,2,3)

print(XI_kron.array[0,1,:,:])  # I don't know how this result comes about
print(XI_ncon.array[0,:,1,:]) #This gives eye(3), the correct one

yes, that's a bug. Thanks Yijian!

Resolving patch:
diff --git a/tensornetwork/linalg/operations.py b/tensornetwork/linalg/operations.py
--- a/tensornetwork/linalg/operations.py
+++ b/tensornetwork/linalg/operations.py
@@ -385,21 +385,46 @@ def pivot(tensor: Tensor, pivot_axis: int = -1) -> Tensor:
   return Tensor(result, backend=backend)
 
 
-def kron(tensorA: Tensor, tensorB: Tensor, pivot_axisA: int = -1,
-         pivot_axisB: int = -1) -> Tensor:
+def kron(tensorA: Tensor, tensorB: Tensor) -> Tensor:
   """
-  Reshape tensorA and tensorB into matrices respectively about pivot_axisA and
-  pivot_axisB, computes the Kronecker product of those matrices, and
-  reshapes to the concatenated shape of tensorA and tensorB
-  (e.g. tensorA -> (2, 3); tensorB -> (4, 5); result -> (2, 3, 4, 5)).
+  Compute the (tensor) kronecker product between `tensorA` and
+  `tensorB`. `tensorA` and `tensorB` can be tensors of any 
+  even order (i.e. `tensorA.ndim % 2 == 0`, `tensorB.ndim % 2 == 0`).
+  The returned tensor has index ordering such that when reshaped into 
+  a matrix with `pivot =t ensorA.ndim//2 + tensorB.ndim//2`, 
+  the resulting matrix is identical to the result of numpy's 
+  `np.kron(matrixA, matrixB)`, with `matrixA, matrixB` matrices 
+  obtained from reshaping `tensorA` and `tensorB` into matrices with 
+  `pivotA = tensorA.ndim//2`, `pivotB = tensorB.ndim//2`
+
+  Example:
+  `tensorA.shape = (2,3,4,5)`, `tensorB.shape(6,7)` ->
+  `kron(tensorA, tensorB).shape = (2, 3, 6, 4, 5, 7)` 
+
+  Args:
+    tensorA: A `Tensor`.
+    tensorB: A `Tensor`.
+  Returns:
+    Tensor: The kronecker product.
+  Raises:
+    ValueError: - If backends, are not matching.
+                - If ndims of the input tensors are not even.
   """
   tensors = [tensorA, tensorA]
   all_backends_same, errstr = _check_backends(tensors, "kron")
   if not all_backends_same:
     raise ValueError(errstr)
+  ndimA, ndimB = tensorA.ndim, tensorB.ndim
+  if ndimA % 2 != 0:
+    raise ValueError(f"kron only supports tensors with even number of legs."
+                     f"found tensorA.ndim = {ndimA}")
+  if ndimB % 2 != 0:
+    raise ValueError(f"kron only supports tensors with even number of legs."
+                     f"found tensorB.ndim = {ndimB}")
   backend = tensorA.backend
-  matrixA = pivot(tensorA, pivot_axis=pivot_axisA)
-  matrixB = pivot(tensorB, pivot_axis=pivot_axisB)
-  arr = backend.einsum("ij,kl->ikjl", matrixA.array, matrixB.array)
-  full_shape = tuple(list(tensorA.shape) + list(tensorB.shape))
-  return Tensor(arr, backend=backend).reshape(full_shape)
+  incoming = list(range(ndimA // 2)) + list(range(ndimA, ndimA + ndimB // 2))
+  outgoing = list(range(ndimA // 2, ndimA)) + list(
+      range(ndimA + ndimB // 2, ndimA + ndimB))
+  arr = backend.transpose(
+      backend.outer_product(tensorA.array, tensorB.array), incoming + outgoing)
+  return Tensor(arr, backend=backend)


Test patch:
diff --git a/tensornetwork/linalg/tests/test_operations.py b/tensornetwork/linalg/tests/test_operations.py
--- a/tensornetwork/linalg/tests/test_operations.py
+++ b/tensornetwork/linalg/tests/test_operations.py
@@ -18,6 +18,10 @@
 import pytest
 import tensornetwork
 import tensornetwork.linalg.operations
+from tensornetwork.linalg.operations import kron
+from tensornetwork.linalg.initialization import random_uniform
+from tensornetwork.tensor import Tensor
+from tensornetwork.ncon_interface import ncon
 from tensornetwork import backends
 from tensornetwork.tests import testing_utils
 
@@ -359,35 +363,46 @@ def test_pivot(backend, dtype, pivotA):
 
 
 @pytest.mark.parametrize("dtype", testing_utils.np_float_dtypes)
-@pytest.mark.parametrize("pivots", ([None, None], [None, 1], [2, None],
-                                    [0, 1]))
-def test_kron(backend, dtype, pivots):
+def test_kron(backend, dtype):
   """ Checks that Tensor.kron() works.
   """
-  pivotA, pivotB = pivots
-  shapeA = (2, 3, 4)
-  shapeB = (1, 2)
-  shapeC = (2, 3, 4, 1, 2)
-  A, _ = testing_utils.safe_randn(shapeA, backend, dtype)
-  if A is not None:
-    B, _ = testing_utils.safe_randn(shapeB, backend, dtype)
-    if pivotA is None and pivotB is None:
-      C = tensornetwork.kron(A, B)
-      matrixA = tensornetwork.pivot(A, pivot_axis=-1)
-      matrixB = tensornetwork.pivot(B, pivot_axis=-1)
-    elif pivotA is None:
-      C = tensornetwork.kron(A, B, pivot_axisB=pivotB)
-      matrixA = tensornetwork.pivot(A, pivot_axis=-1)
-      matrixB = tensornetwork.pivot(B, pivot_axis=pivotB)
-    elif pivotB is None:
-      C = tensornetwork.kron(A, B, pivot_axisA=pivotA)
-      matrixA = tensornetwork.pivot(A, pivot_axis=pivotA)
-      matrixB = tensornetwork.pivot(B, pivot_axis=-1)
-    else:
-      C = tensornetwork.kron(A, B, pivot_axisA=pivotA, pivot_axisB=pivotB)
-      matrixA = tensornetwork.pivot(A, pivot_axis=pivotA)
-      matrixB = tensornetwork.pivot(B, pivot_axis=pivotB)
-
-    Ctest = C.backend.einsum("ij,kl->ikjl", matrixA.array, matrixB.array)
-    Ctest = C.backend.reshape(Ctest, shapeC)
-    np.testing.assert_allclose(C.array, Ctest)
+  if (backend == "pytorch" and dtype in (np.complex64, np.complex128)):
+    pytest.skip("pytorch support for complex dtypes is currently poor.")
+
+  np.random.seed(10)
+  t1 = Tensor(np.random.rand(2, 2).astype(dtype), backend=backend)
+  t2 = Tensor(np.random.rand(3, 3).astype(dtype), backend=backend)
+
+  res_kron = kron(t1, t2)
+  res_ncon = ncon([t1.array, t2.array], [[-1, -3], [-2, -4]], backend=backend)
+  np.testing.assert_allclose(res_kron.array, res_ncon)
+  mat1 = res_kron.reshape((6, 6))
+  mat2 = np.kron(t1.array, t2.array)
+  np.testing.assert_allclose(mat1.array, mat2)
+
+  t1 = Tensor(np.random.rand(2, 2, 2, 2).astype(dtype), backend=backend)
+  t2 = Tensor(np.random.rand(3, 3, 3, 3).astype(dtype), backend=backend)
+  res_kron = kron(t1, t2)
+  res_ncon = ncon([t1.array, t2.array], [[-1, -2, -5, -6], [-3, -4, -7, -8]],
+                  backend=backend)
+  np.testing.assert_allclose(res_kron.array, res_ncon)
+  mat1 = res_kron.reshape((36, 36))
+  mat2 = np.kron(
+      np.array(t1.array).reshape(4, 4),
+      np.array(t2.array).reshape(9, 9))
+  np.testing.assert_allclose(mat1.array, mat2)
+
+
+def test_kron_raises(backend):
+  np.random.seed(10)
+  t1 = Tensor(np.random.rand(2, 2, 2), backend=backend)
+  t2 = Tensor(np.random.rand(3, 3), backend=backend)
+  with pytest.raises(ValueError, match="tensorA.ndim"):
+    kron(t1, t2)
+  with pytest.raises(ValueError, match="tensorB.ndim"):
+    kron(t2, t1)
+
+  t1 = Tensor(np.random.rand(2, 2, 2), backend='numpy')
+  t2 = Tensor(np.random.rand(3, 3), backend='tensorflow')
+  with pytest.raises(ValueError, match="kron"):
+    kron(t1, t2)