code: def tensor_ring_als(
      tensor,
      rank,
      ls_solve="lstsq",
      n_iter_max=100,
      tol=1e-6,
      random_state=None,
      verbose=False,
      callback=None,
  ):
      """Tensor ring decomposition via alternating least squares (ALS)
  
      Computes a rank-`rank` tensor ring decomposition of `tensor` using ALS. The
      implementation roughly follows Algorithm 2 in [1]_.
  
      Parameters
      ----------
      tensor : ndarray
      rank : Union[int, List[int]]
          The rank of the decomposition. If `rank` is an int, then all ranks will be the
          same and equal to `rank`. If `rank` is a list, then the i-th core will be of
          size rank[i]-by-shape[i]-by-rank[i+1], where shape[i] is the dimension of the
          i-th mode of `tensor`.
      ls_solve : {"lstsq", "normal_eq"}, default is "lstsq"
          When equal to "lstsq", the least squares problems are solved as overdetermined
          systems of equations using tl.lstsq. When equal to "normal_eq", a normal
          equation formulation is used instead together with tl.solve. This latter
          approach is expected to yield poorer accuracy due to numerical issuse with the
          normal equations.
      n_iter_max : int, default is 100
          Maximum number of ALS iterations.
      tol : float, default 1e-6
          The algorithm is terminated when the change in the relative reconstruction error
          is less than `tol`.
      random_state : {None, int, np.random.RandomState}
          Used to set the random seed in the algorithm.
      verbose : bool, default False
          If True, the algorithm will make some additional print outs.
      callback : {None, Callable[[tl.tr_tensor.TRTensor, float], {None, bool}]}, default None
          A callback function which can be used for, e.g., logging the per-iteration
          error. Such a function takes two inputs: A tensor ring decomposition and a float
          which indicates the relative reconstruction error between `tensor` and the
          inputted TRTensor. It can return a bool which can be used to control when the
          ALS algorithm terminates.
  
      Returns
      -------
      tr_decomp : tl.tr_tensor.TRTensor
          The tensor ring decomposition computed by the algorithm.
  
      References
      ----------
      .. [1] Q. Zhao, G. Zhou, S. Xie, L. Zhang, A. Cichocki, "Tensor Ring Decomposition",
             arXiv:1606.05535, 2016.
      """
  
      shape = tl.shape(tensor)
      rank = validate_tr_rank(shape, rank=rank)
      n_dim = len(shape)
      rng = tl.check_random_state(random_state)
      if tol > 0 or callback:
          tensor_norm = tl.norm(tensor)
      valid_ls_solve = {"lstsq", "normal_eq"}
      if ls_solve not in valid_ls_solve:
          raise ValueError(
              f"Invalid value provided for ls_solve. It must be in {valid_ls_solve}."
          )
  
      # Randomly initialize decomposition cores
      tr_decomp = tl.random.random_tr(
          shape, rank, random_state=rng, **tl.context(tensor)
      )
  
      # Run callback function if provided
      if callback:
          rel_error = (
              tl.norm(tl.tr_to_tensor(tr_decomp) - tensor) / tensor_norm
          )
          callback(tr_decomp, rel_error)
  
      # Main loop
      rec_errors = []
      for iter in range(n_iter_max):
          for dim in range(n_dim):
              # Compute appropriate transposed unfolding of tensor
              tensor_unf = matricize(
                  tensor, [n for n in range(n_dim) if n != dim], [dim]
              )
  
              # Compute design matrix
              subchain_tensor = tr_decomp[(dim + 1) % n_dim]
              for j in range(2, n_dim):
                  subchain_tensor = tl.tensordot(
                      subchain_tensor,
                      tr_decomp[(dim + j) % n_dim],
                      axes=1,
                  )
              tr_idx = (
                  [i + n_dim - dim for i in range(dim)]
                  + [i + 1 for i in range(n_dim - dim - 1)]
                  + [n_dim, 0]
              )
              subchain_tensor = tl.transpose(subchain_tensor, tr_idx)
              design_mat = tl.reshape(
                  subchain_tensor, (-1, rank[dim] * rank[dim + 1])
              )
  
              if ls_solve == "lstsq":
                  # Solve least squares problem directly
                  sol, *_ = tl.lstsq(design_mat, tensor_unf)
  
              elif ls_solve == "normal_eq":
                  # Solve least squares problem via normal equations
                  design_mat_tr = tl.transpose(design_mat)
                  gram_mat = tl.matmul(design_mat_tr, design_mat)
                  rhs_mat = tl.matmul(design_mat_tr, tensor_unf)
                  sol = tl.solve(gram_mat, rhs_mat)
  
              # Update core
              tr_decomp[dim] = tl.transpose(
                  tl.reshape(
                      sol, (rank[dim], rank[dim + 1], shape[dim])
                  ),
                  [0, 2, 1],
              )
  
          # Compute relative error if necessary
          if tol > 0 or callback:
              error = tl.norm(tl.matmul(design_mat, sol) - tensor_unf)
              rel_error = error / tensor_norm
              rec_errors.append(rel_error)
              if iter >= 1:
                  rel_error_decrease = rec_errors[-2] - rec_errors[-1]
              if verbose:
                  if iter >= 1:
                      print(
                          f"Iteration {iter+1} finished. Reconstruction error: {rel_error}, decrease = {rel_error_decrease}, unnormalized = {error}"
                      )
                  else:
                      print(
                          f"Iteration {iter+1} finished. Reconstruction error: {rel_error}, unnormalized = {error}"
                      )
          elif verbose:
              print(f"Iteration {iter+1} finished.")
  
          # Run callback function if provided
          if callback:
              callback_retVal = callback(tr_decomp, rel_error)
              if callback_retVal:
                  if verbose:
                      print(
                          "Received True from callback function. Exiting."
                      )
                  break
  
          # Check convergence
          if tol > 0 and iter >= 1:
              if rel_error_decrease < tol:
                  if verbose:
                      print(
                          f"tensor_ring_als converged after {iter} iterations."
                      )
                  break
  
      return tr_decomp
  
file_path: tensorly/tensorly/decomposition/_tr.py
imports: import tensorly
  from ._base_decomposition import DecompositionMixin
  from ..base import matricize
  from ..tr_tensor import validate_tr_rank, TRTensor
  from ..tenalg.svd import svd_interface
label_desc: Use image node_id 2 for calling a global function with example usage: tensor_ring_als(tensor, rank, ls_solve, n_iter_max, tol, random_state, verbose, callback) and returns: tr_decomp