code: def GPA(
      A,
      ff=None,
      vgQ=None,
      T=None,
      max_tries=501,
      rotation_method="orthogonal",
      tol=1e-5,
  ):
      r"""
      The gradient projection algorithm (GPA) minimizes a target function
      :math:`\phi(L)`, where :math:`L` is a matrix with rotated factors.
  
      For orthogonal rotation methods :math:`L=AT`, where :math:`T` is an
      orthogonal matrix. For oblique rotation matrices :math:`L=A(T^*)^{-1}`,
      where :math:`T` is a normal matrix, i.e., :math:`TT^*=T^*T`. Oblique
      rotations relax the orthogonality constraint in order to gain simplicity
      in the interpretation.
  
      Parameters
      ----------
      A : numpy matrix
          non rotated factors
      T : numpy matrix (default identity matrix)
          initial guess of rotation matrix
      ff : function (defualt None)
          criterion :math:`\phi` to optimize. Should have A, T, L as keyword
          arguments
          and mapping to a float. Only used (and required) if vgQ is not
          provided.
      vgQ : function (defualt None)
          criterion :math:`\phi` to optimize and its derivative. Should have
           A, T, L as keyword arguments and mapping to a tuple containing a
          float and vector. Can be omitted if ff is provided.
      max_tries : int (default 501)
          maximum number of iterations
      rotation_method : str
          should be one of {orthogonal, oblique}
      tol : float
          stop criterion, algorithm stops if Frobenius norm of gradient is
          smaller then tol
      """
      # pre processing
      if rotation_method not in ["orthogonal", "oblique"]:
          raise ValueError(
              "rotation_method should be one of "
              "{orthogonal, oblique}"
          )
      if vgQ is None:
          if ff is None:
              raise ValueError("ff should be provided if vgQ is not")
          derivative_free = True
          Gff = lambda x: Gf(x, lambda y: ff(T=y, A=A, L=None))
      else:
          derivative_free = False
      if T is None:
          T = np.eye(A.shape[1])
      # pre processing for iteration
      al = 1
      table = []
      # pre processing for iteration: initialize f and G
      if derivative_free:
          f = ff(T=T, A=A, L=None)
          G = Gff(T)
      elif rotation_method == "orthogonal":  # and not derivative_free
          L = A.dot(T)
          f, Gq = vgQ(L=L)
          G = (A.T).dot(Gq)
      else:  # i.e. rotation_method == 'oblique' and not derivative_free
          Ti = np.linalg.inv(T)
          L = A.dot(Ti.T)
          f, Gq = vgQ(L=L)
          G = -((L.T).dot(Gq).dot(Ti)).T
      # iteration
      for i_try in range(0, max_tries):
          # determine Gp
          if rotation_method == "orthogonal":
              M = (T.T).dot(G)
              S = (M + M.T) / 2
              Gp = G - T.dot(S)
          else:  # i.e. if rotation_method == 'oblique':
              Gp = G - T.dot(np.diag(np.sum(T * G, axis=0)))
          s = np.linalg.norm(Gp, "fro")
          table.append([i_try, f, np.log10(s), al])
          # if we are close stop
          if s < tol:
              break
          # update T
          al = 2 * al
          for i in range(11):
              # determine Tt
              X = T - al * Gp
              if rotation_method == "orthogonal":
                  U, D, V = np.linalg.svd(X, full_matrices=False)
                  Tt = U.dot(V)
              else:  # i.e. if rotation_method == 'oblique':
                  v = 1 / np.sqrt(np.sum(X**2, axis=0))
                  Tt = X.dot(np.diag(v))
              # calculate objective using Tt
              if derivative_free:
                  ft = ff(T=Tt, A=A, L=None)
              elif (
                  rotation_method == "orthogonal"
              ):  # and not derivative_free
                  L = A.dot(Tt)
                  ft, Gq = vgQ(L=L)
              else:  # i.e. rotation_method == 'oblique' and not derivative_free
                  Ti = np.linalg.inv(Tt)
                  L = A.dot(Ti.T)
                  ft, Gq = vgQ(L=L)
              # if sufficient improvement in objective -> use this T
              if ft < f - 0.5 * s**2 * al:
                  break
              al = al / 2
          # post processing for next iteration
          T = Tt
          f = ft
          if derivative_free:
              G = Gff(T)
          elif (
              rotation_method == "orthogonal"
          ):  # and not derivative_free
              G = (A.T).dot(Gq)
          else:  # i.e. rotation_method == 'oblique' and not derivative_free
              G = -((L.T).dot(Gq).dot(Ti)).T
      # post processing
      Th = T
      Lh = rotateA(A, T, rotation_method=rotation_method)
      Phi = (T.T).dot(T)
      return Lh, Phi, Th, table
  
file_path: statsmodels/statsmodels/multivariate/factor_rotation/_gpa_rotation.py
imports: import numpy
label_desc: Use image node_id 1 for calling a global function with example usage: GPA(A, ff, vgQ, T, max_tries, rotation_method, tol) and returns: Lh,  Phi,  Th,  table