code: def gram_schmidt(matrix, return_opt='orthonormal'):
      r
  
      if return_opt not in ('orthonormal', 'orthogonal', 'both'):
          raise ValueError('Invalid return_opt, options are: "orthonormal", '
                           '"orthogonal" or "both"')
  
      u = []
      e = []
  
      for vector in matrix:
  
          if len(u) == 0:
              u_now = vector
          else:
              u_now = vector - sum([project(u_i, vector) for u_i in u])
  
          u.append(u_now)
          e.append(u_now / np.linalg.norm(u_now, 2))
  
      u = np.array(u)
      e = np.array(e)
  
      if return_opt == 'orthonormal':
          return e
      elif return_opt == 'orthogonal':
          return u
      elif return_opt == 'both':
          return u, e
docs: r"""Gram-Schmit
  
      This method orthonormalizes the row vectors of the input matrix.
  
      Parameters
      ----------
      matrix : np.ndarray
          Input matrix array
      return_opt : str {orthonormal, orthogonal, both}
          Option to return u, e or both.
  
      Returns
      -------
      Lists of orthogonal vectors, u, and/or orthonormal vectors, e
  
      Examples
      --------
      >>> from modopt.math.matrix import gram_schmidt
      >>> a = np.arange(9).reshape(3, 3)
      >>> gram_schmidt(a)
      array([[ 0.        ,  0.4472136 ,  0.89442719],
             [ 0.91287093,  0.36514837, -0.18257419],
             [-1.        ,  0.        ,  0.        ]])
  
      Notes
      -----
      Implementation from:
      https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process