% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/efa_procrustes.R
\name{efa_procrustes}
\alias{efa_procrustes}
\title{Rotate a loading matrix to a target using Procrustes alignment}
\usage{
efa_procrustes(
  A,
  Target,
  rotation = c("orthogonal", "oblique"),
  S = NULL,
  T_init = NULL,
  oblique_eps = 1e-05,
  oblique_maxit = 1000,
  oblique_max_line_search = 10,
  oblique_step0 = 1,
  oblique_normalize = FALSE,
  oblique_random_starts = 0,
  oblique_screen_keep = 2,
  oblique_triage_maxit = 25,
  oblique_triage_improve_tol = 0
)
}
\arguments{
\item{A}{Numeric loading matrix to be aligned.}

\item{Target}{Numeric target matrix with the same dimensions as \code{A}.}

\item{rotation}{Character string, either \code{"orthogonal"} or \code{"oblique"}.}

\item{S}{Optional \verb{k x k} cross-product matrix \code{crossprod(A)}, kept for
compatibility. It enters both the oblique criterion and its gradient, so any
other matrix would minimize a different criterion: where \code{S} is used it is
checked against \code{crossprod(A)} and must agree with it up to a relative
tolerance of \code{1e-8}. That check forms \code{crossprod(A)} itself, so passing \code{S}
no longer avoids any work: omitting it gives the same result for slightly
less. \code{S} is used, and therefore checked, only on the oblique path with more
than one factor and \code{oblique_normalize = FALSE}; if Kaiser normalization is
requested, the cross-product must be recomputed on the normalized matrix and
\code{S} is ignored.}

\item{T_init}{Optional \verb{k x k} starting transformation matrix for the oblique
solver. Its columns are normalized internally, and the normalized matrix must
be well enough conditioned to define a proper factor correlation matrix: its
smallest singular value must be at least \code{1e-4}, the same floor the solver
applies to every candidate it evaluates. If \code{NULL} (the default), the oblique
solver is warm-started from the closed-form orthogonal Procrustes solution.}

\item{oblique_eps}{Positive convergence tolerance for the projected-gradient
norm in the oblique solver.}

\item{oblique_maxit}{Non-negative integer. Maximum number of projected-gradient
updates in the full oblique solver.}

\item{oblique_max_line_search}{Non-negative integer. Maximum number of
step-halving attempts after the initial line-search step.}

\item{oblique_step0}{Positive initial step size for the oblique solver.}

\item{oblique_normalize}{Logical; if \code{TRUE}, apply Kaiser row normalization to
the loadings (only) in the oblique solver and back-transform the aligned
loadings afterwards, leaving \code{Target} unnormalized (as in
\code{GPArotation::targetQ(normalize = TRUE)}).}

\item{oblique_random_starts}{Non-negative integer. Number of additional random
starts used by the oblique solver.}

\item{oblique_screen_keep}{Non-negative integer. Number of random starts
retained after cheap objective screening and sent to triage optimization.}

\item{oblique_triage_maxit}{Non-negative integer. Number of short optimization
iterations used in the triage stage.}

\item{oblique_triage_improve_tol}{Non-negative scalar. Relative improvement
required for a triaged start to be promoted to full optimization.}
}
\value{
A list. Every path returns the following components:

\item{loadings}{Aligned loading matrix.}
\item{T}{Transformation matrix.}
\item{Phi}{Factor intercorrelation matrix; the identity for orthogonal and
one-factor alignment.}
\item{value}{Target criterion at the returned solution.}
\item{convergence}{Logical; \code{TRUE} for the closed-form orthogonal solution.}
\item{valid}{Logical; whether the transformation defines an admissible \code{Phi}.}
\item{iterations}{Number of solver iterations; \code{0} for the closed-form
orthogonal solution.}
\item{kappa_T}{Condition number of \code{T}; a constant \code{1} on the orthogonal path.}
\item{Table}{Iteration history with columns \code{iter}, \code{f}, \code{log10_s}, and \code{step};
a single placeholder row on the orthogonal path.}
\item{method}{\code{"orthogonal_procrustes"}, \code{"oblique_procrustes"}, or
\code{"single_factor_procrustes"} for a one-factor oblique request.}
\item{line_search_failed}{Logical line-search diagnostic.}
\item{best_start_index, all_start_indices, all_values, all_converged,
all_iterations}{Multi-start summary of the starts that were fully
optimized; each has a single entry when no random starts were used.}

The oblique solver additionally returns \code{screen_start_indices} and
\code{screen_values} (the starts kept by cheap objective screening and their
criterion values) together with the counts \code{n_random_starts}, \code{n_screened},
\code{n_triaged}, and \code{n_fully_optimized}. These six components are absent for
\code{rotation = "orthogonal"} and for one-factor models, which are aligned with the
orthogonal solution.

Row and column names are preserved where possible. When
\code{oblique_normalize = TRUE} the returned \code{loadings} are back-transformed to the
original scale, but \code{value} is the criterion on the Kaiser-normalized loadings,
so it is not \code{0.5 * sum((loadings - Target)^2)}.
}
\description{
\code{efa_procrustes()} aligns one loading matrix to a target loading matrix with the
same dimensions. It is used internally by \code{efa_mi()}, but can also be used
directly when factor columns must be brought into a common orientation before
averaging or comparing solutions.
}
\details{
For \code{rotation = "orthogonal"}, the function solves the closed-form orthogonal
Procrustes problem

\deqn{\min_T \frac{1}{2}\|A T - B\|_F^2 \quad \textrm{subject to}\quad T'T = I,}

where \code{A} is the loading matrix and \code{B} is \code{Target}.

For \code{rotation = "oblique"}, the function calls the compiled
\code{.oblique_procrustes()} optimizer. The oblique convention is the same as in
\code{GPArotation::targetQ()}:

\deqn{L = A T^{-T}, \qquad \Phi = T'T, \qquad diag(\Phi) = 1.}

By default the oblique solver is warm-started from the closed-form orthogonal
Procrustes solution, which resolves the factor permutation and sign
indeterminacy and avoids the poor local minima an identity start can fall
into. Supply \code{T_init} to override this start. Random starts are only used for
oblique alignment. For one-factor models, oblique and orthogonal alignment are
equivalent, so the function uses the stable one-factor orthogonal solution
instead of calling the oblique optimizer.
}
\examples{
## Align an estimated loading matrix to a known target pattern: fit an
## unrotated three-factor model, then rotate its loadings toward the true
## population pattern.
efa_mod <- efa_fit(test_models$baseline$cormat, N = 500, n_factors = 3,
                   estimator = "PAF", rotation = "none")
target <- population_models$loadings$baseline

## Orthogonal target rotation (rigid rotation/reflection):
efa_procrustes(efa_mod$unrot_loadings, target, rotation = "orthogonal")

## Oblique target rotation (lets the aligned factors correlate):
efa_procrustes(efa_mod$unrot_loadings, target, rotation = "oblique")

}
\seealso{
Other factor rotation:
\code{\link[=efa_schmid_leiman]{efa_schmid_leiman()}}
}
\concept{factor rotation}
