class_docstr: Kronecker product between two `LinearOperators`.
  
  This operator composes one or more linear operators `[op1,...,opJ]`,
  building a new `LinearOperator` representing the Kronecker product:
  `op1 x op2 x .. opJ` (we omit parentheses as the Kronecker product is
  associative).
  
  If `opj` has shape `batch_shape_j + [M_j, N_j]`, then the composed operator
  will have shape equal to `broadcast_batch_shape + [prod M_j, prod N_j]`,
  where the product is over all operators.
  
  ```python
  # Create a 4 x 4 linear operator composed of two 2 x 2 operators.
  operator_1 = LinearOperatorFullMatrix([[1., 2.], [3., 4.]])
  operator_2 = LinearOperatorFullMatrix([[1., 0.], [2., 1.]])
  operator = LinearOperatorKronecker([operator_1, operator_2])
  
  operator.to_dense()
  ==> [[1., 0., 2., 0.],
       [2., 1., 4., 2.],
       [3., 0., 4., 0.],
       [6., 3., 8., 4.]]
  
  tensor_shape.TensorShape(operator.shape)
  ==> [4, 4]
  
  operator.log_abs_determinant()
  ==> scalar Tensor
  
  x = ... Shape [4, 2] Tensor
  operator.matmul(x)
  ==> Shape [4, 2] Tensor
  
  # Create a [2, 3] batch of 4 x 5 linear operators.
  matrix_45 = tf.random.normal(shape=[2, 3, 4, 5])
  operator_45 = LinearOperatorFullMatrix(matrix)
  
  # Create a [2, 3] batch of 5 x 6 linear operators.
  matrix_56 = tf.random.normal(shape=[2, 3, 5, 6])
  operator_56 = LinearOperatorFullMatrix(matrix_56)
  
  # Compose to create a [2, 3] batch of 20 x 30 operators.
  operator_large = LinearOperatorKronecker([operator_45, operator_56])
  
  # Create a shape [2, 3, 20, 2] vector.
  x = tf.random.normal(shape=[2, 3, 6, 2])
  operator_large.matmul(x)
  ==> Shape [2, 3, 30, 2] Tensor
  ```
  
  #### Performance
  
  The performance of `LinearOperatorKronecker` on any operation is equal to
  the sum of the individual operators' operations.
  
  #### Matrix property hints
  
  This `LinearOperator` is initialized with boolean flags of the form `is_X`,
  for `X = non_singular, self_adjoint, positive_definite, square`.
  These have the following meaning:
  
  * If `is_X == True`, callers should expect the operator to have the
    property `X`.  This is a promise that should be fulfilled, but is *not* a
    runtime assert.  For example, finite floating point precision may result
    in these promises being violated.
  * If `is_X == False`, callers should expect the operator to not have `X`.
  * If `is_X == None` (the default), callers should have no expectation either
    way.
code: def _solve_matmul_internal(
      self, x, solve_matmul_fn, adjoint=False, adjoint_arg=False
  ):
      # We heavily rely on Roth's column Lemma [1]:
      # (A x B) * vec X = vec BXA^T
      # where vec stacks all the columns of the matrix under each other.
      # In our case, we use a variant of the lemma that is row-major
      # friendly: (A x B) * vec' X = vec' AXB^T
      # Where vec' reshapes a matrix into a vector. We can repeatedly apply this
      # for a collection of kronecker products.
      # Given that (A x B)^-1 = A^-1 x B^-1 and (A x B)^T = A^T x B^T, we can
      # use the above to compute multiplications, solves with any composition of
      # transposes.
      output = x
  
      if adjoint_arg:
          if np.issubdtype(self.dtype, np.complexfloating):
              output = math_ops.conj(output)
      else:
          output = linalg.transpose(output)
  
      for o in reversed(self.operators):
          # Statically compute the reshape.
          if adjoint:
              operator_dimension = o.range_dimension_tensor()
          else:
              operator_dimension = o.domain_dimension_tensor()
          output_shape = _prefer_static_shape(output)
  
          if ops.get_static_value(operator_dimension) is not None:
              operator_dimension = ops.get_static_value(
                  operator_dimension
              )
              if (
                  tensor_shape.TensorShape(output.shape)[-2] is not None
                  and tensor_shape.TensorShape(output.shape)[-1]
                  is not None
              ):
                  dim = int(
                      tensor_shape.TensorShape(output.shape)[-2]
                      * output_shape[-1]
                      // operator_dimension
                  )
          else:
              dim = _ops.cast(
                  output_shape[-2]
                  * output_shape[-1]
                  // operator_dimension,
                  dtype=dtypes.int32,
              )
  
          output_shape = _prefer_static_concat_shape(
              output_shape[:-2], [dim, operator_dimension]
          )
          output = array_ops.reshape(output, shape=output_shape)
  
          # Conjugate because we are trying to compute A @ B^T, but
          # `LinearOperator` only supports `adjoint_arg`.
          if np.issubdtype(self.dtype, np.complexfloating):
              output = math_ops.conj(output)
  
          output = solve_matmul_fn(
              o, output, adjoint=adjoint, adjoint_arg=True
          )
  
      if adjoint_arg:
          col_dim = _prefer_static_shape(x)[-2]
      else:
          col_dim = _prefer_static_shape(x)[-1]
  
      if adjoint:
          row_dim = self.domain_dimension_tensor()
      else:
          row_dim = self.range_dimension_tensor()
  
      matrix_shape = [row_dim, col_dim]
  
      output = array_ops.reshape(
          output,
          _prefer_static_concat_shape(
              _prefer_static_shape(output)[:-2], matrix_shape
          ),
      )
  
      if tensor_shape.TensorShape(x.shape).is_fully_defined():
          if adjoint_arg:
              column_dim = tensor_shape.TensorShape(x.shape)[-2]
          else:
              column_dim = tensor_shape.TensorShape(x.shape)[-1]
          broadcast_batch_shape = common_shapes.broadcast_shape(
              tensor_shape.TensorShape(x.shape)[:-2], self.batch_shape
          )
          if adjoint:
              matrix_dimensions = [self.domain_dimension, column_dim]
          else:
              matrix_dimensions = [self.range_dimension, column_dim]
  
          tensorshape_util.set_shape(
              output,
              broadcast_batch_shape.concatenate(matrix_dimensions),
          )
  
      return output
  
imports: from tensorflow_probability.python.internal.backend.numpy import ops
  from tensorflow_probability.python.internal.backend.numpy import dtype
  from tensorflow_probability.python.internal.backend.numpy import errors
  from tensorflow_probability.python.internal.backend.numpy import ops
  from tensorflow_probability.python.internal.backend.numpy.gen import tensor_shape
  from tensorflow_probability.python.internal.backend.numpy import ops
  from tensorflow_probability.python.internal.backend.numpy import numpy_array
  from tensorflow_probability.python.internal.backend.numpy import debugging
  from tensorflow_probability.python.internal.backend.numpy import control_flow
  from tensorflow_probability.python.internal.backend.numpy import numpy_math
  from tensorflow_probability.python.internal.backend.numpy import linalg_impl
  from tensorflow_probability.python.internal.backend.numpy.gen import linear_operator
  import numpy
  from tensorflow_probability.python.internal.backend.numpy import linalg_impl
  from tensorflow_probability.python.internal.backend.numpy import ops
  from tensorflow_probability.python.internal.backend.numpy.gen import tensor_shape
  from tensorflow_probability.python.internal.backend.numpy import private
label_desc: Use image node_id 8 for calling the LinearOperatorKronecker obj's underlying member method code with example usage: obj._solve_matmul_internal(x, solve_matmul_fn, adjoint, adjoint_arg) and returns: output