\encoding{UTF-8}
\name{lp}
\alias{Lp rotation}
\alias{GPForth.lp}
\alias{GPFoblq.lp}

\title{\eqn{L^p}{Lp} Rotation}
\usage{
GPForth.lp(A, Tmat = diag(ncol(A)), p = 1, normalize = FALSE, eps = 1e-05,
           maxit = 10000, gpaiter = 5)
GPFoblq.lp(A, Tmat = diag(ncol(A)), p = 1, normalize = FALSE, eps = 1e-05,
           maxit = 10000, gpaiter = 5)
}

\arguments{
    \item{A}{Initial factor loadings matrix to be rotated.}
    \item{Tmat}{Initial rotation matrix.}
    \item{p}{Component-wise \eqn{L^p}{Lp} where 0 < p \eqn{\le}{<=} 1.}
    \item{normalize}{Not recommended for \eqn{L^p}{Lp} rotation.}
    \item{eps}{Convergence is assumed when the norm of the gradient is smaller
         than \code{eps}.}
    \item{maxit}{Maximum number of iterations allowed in the main loop.}
    \item{gpaiter}{Maximum iterations for the inner GPA rotation loop.
         The goal is to decrease the objective value, not fully optimize the
         inner loop. Warnings may appear but can be ignored if the main loop
         converges.}
}

\value{
  A \code{GPArotation} object, which is a list containing:
  \item{loadings}{Rotated loadings matrix, with one column per factor.}
  \item{Th}{Rotation matrix, satisfying \code{loadings \%*\% t(Th) = A}.}
  \item{Table}{Data frame recording iteration details: iteration count,
       objective value, and elapsed time.}
  \item{method}{String indicating the rotation objective function.}
  \item{orthogonal}{Logical indicating whether the rotation is orthogonal.}
  \item{convergence}{Logical indicating whether convergence was achieved.
       Convergence is assessed element-wise using \code{eps} as tolerance.}
  \item{Phi}{Covariance matrix of rotated factors, \code{t(Th) \%*\% Th}.}
}

\description{
  Performs \eqn{L^p}{Lp} rotation to obtain sparse factor loadings.
}

\details{
These functions optimize an \eqn{L^p}{Lp} rotation objective where
\code{0 < p <= 1}. A smaller value of \code{p} promotes greater sparsity
in the loading matrix but increases computational difficulty. For 
guidance on choosing \code{p}, see the Concluding Remarks in
Liu et al. (2023).

The user-facing wrapper functions \code{\link{lpT}} and \code{\link{lpQ}}
provide random start functionality on top of \code{GPForth.lp} and
\code{GPFoblq.lp} respectively, analogous to how \code{\link{GPFRSorth}}
and \code{\link{GPFRSoblq}} wrap \code{GPForth} and \code{GPFoblq} for
standard rotation criteria. For most users \code{lpT} and \code{lpQ}
are the recommended entry points.

Since the \eqn{L^p}{Lp} objective is nonsmooth, a different optimization
method is required compared to smooth rotation criteria. \code{GPForth.lp}
and \code{GPFoblq.lp} replace \code{GPForth} and \code{GPFoblq} for
orthogonal and oblique \eqn{L^p}{Lp} rotations, respectively.

The optimization uses an iterative reweighted least squares (IRLS) approach.
The nonsmooth objective is approximated by a smooth weighted least squares
function in the outer loop, which is then optimized using GPA in the inner
loop (\code{gpaiter} controls the maximum inner iterations).

Normalization is not recommended for \eqn{L^p}{Lp} rotation and may
produce unexpected results.

}
\examples{
  data("WansbeekMeijer", package = "GPArotation")
  fa.unrotated <- factanal(factors = 2, covmat = NetherlandsTV, rotation = "none")
  options(warn = -1)

  # Orthogonal rotation — single start
  fa.lpT1 <- GPForth.lp(loadings(fa.unrotated), p = 1)

  # Orthogonal rotation — 10 random starts
  fa.lpT <- lpT(loadings(fa.unrotated), Tmat = Random.Start(2), p = 1,
                randomStarts = 10)
  print(fa.lpT, digits = 5, sortLoadings = FALSE, Table = TRUE, rotateMat = TRUE)

  # Oblique rotation — single start
  fa.lpQ1 <- GPFoblq.lp(loadings(fa.unrotated), p = 1)

  # Oblique rotation — 10 random starts
  fa.lpQ <- lpQ(loadings(fa.unrotated), p = 1, randomStarts = 10)
  summary(fa.lpQ, Structure = TRUE)

  # Compare Lp (p=1), Lp (p=0.5), and Geomin oblique rotations
  set.seed(1020)
  fa.lpQ1   <- lpQ(loadings(fa.unrotated), p = 1,   randomStarts = 10)
  fa.lpQ0.5 <- lpQ(loadings(fa.unrotated), p = 0.5, randomStarts = 10)
  fa.geo    <- geominQ(loadings(fa.unrotated),       randomStarts = 10)

  # With factor ordering using internal sortGPALoadings helper
  res <- round(cbind(GPArotation:::.sortGPALoadings(fa.lpQ1)$loadings,
                     GPArotation:::.sortGPALoadings(fa.lpQ0.5)$loadings,
                     GPArotation:::.sortGPALoadings(fa.geo)$loadings), 3)
  print(c("oblique --  Lp p=1        Lp p=0.5       Geomin"))
  print(res)

  # Without factor ordering
  res <- round(cbind(fa.lpQ1$loadings, fa.lpQ0.5$loadings, fa.geo$loadings), 3)
  print(c("oblique --  Lp p=1        Lp p=0.5       Geomin"))
  print(res)

  options(warn = 0)
}

\seealso{
  \code{\link{lpT}},
  \code{\link{lpQ}},
  \code{\link{vgQ.lp.wls}}
}

\references{
  Liu, X., Wallin, G., Chen, Y., and Moustaki, I. (2023). Rotation to
    sparse loadings using \eqn{L^p}{Lp} losses and related inference
    problems. \emph{Psychometrika}, \bold{88}(2), 527--553.
    doi: 10.1007/s11336-023-09911-y
}

\author{Xinyi Liu, with minor modifications for GPArotation by C. Bernaards.}

\concept{rotation}
\keyword{multivariate}