Changelog
Source:NEWS.md
genpca 0.2.0
Release review fixes
Randomized sketches use Gaussian draws and use a deterministic complete basis when the sketch spans a full matrix dimension. Metric orthonormalization verifies the original Gram matrix, corrects residual normalization error, and removes dependent sketch directions without inventing rank through jitter. This fixes seed- and C++-library-dependent component loss on Windows.
gpca_mle()now separates the penalty effect of reciprocal rescaling (loglik_rescale_delta, exactly zero withscale_fix = "none") from final refitting and objective reevaluation (loglik_refit_delta). The reported likelihood remains evaluated at the returned fit and metrics.Strictly positive diagonal weights are retained in both forward and inverse metric factors. Previously a small positive weight could contribute a large singular value while its recovered loading was zero. The fix covers GPCA, covariance PCA and PLS operators, including base-matrix diagonal inputs.
Both deflation implementations now separate
rank_rtolfrom the iterationthreshold. All backends also apply a common final component filter. Empty C++ deflation results return empty singular-value and variance vectors.Incomplete eigencore results raise
genpca_solver_nonconvergence. Existing dense fallbacks handle this error; operator callers without a fallback stop instead of returning an unchecked fit.Explicit
"clip"repairs remove negative eigenvalues even within the PSD validation tolerance and report the change, up to reconstruction roundoff.geigen_cov()documents its projected equation for singular metrics and the range constraint on its vectors. Commuting metrics and covariances need not give the same leading component under GMD and generalized eigenanalysis.Rank-cutoff equations render in both PDF and HTML manuals. Local diagnostic artifacts are excluded from source packages.
Numerical semantics (audit of 2026-09)
- All rank and validity decisions are now relative to the scale of the problem, so results are invariant to rescaling
Xor a metric. Newrank_rtolargument ingenpca(),genpca_cov()and the internal solvers: componentjis dropped whend_j <= rank_rtol * d_1(default1e-6), for every method. Metric validation and null-space detection use a separate relative tolerance,sqrt(.Machine$double.eps). The previous absolute cutoffs (1e-8on eigenvalues,1e-9on singular values, a1e-8total-variance floor, an absolutejitter_metric) could change the number of components returned, or return wrong values from the randomized backend, whenXwas rescaled. - Positive semi-definiteness and positive definiteness are now distinct checks (
is_psd(),is_pd(), both shifted Cholesky probes with relative tolerance).ensure_spd()always returns a positive definite matrix and forwards its tolerance; previously a zero or singular matrix could be returned unchanged. - Asymmetric metrics are an error under every
constraints_remedy. The relative asymmetry||A - A'|| / ||A||is measured; below1e-10the two triangles are averaged, above it the call stops. Previously one triangle was silently used, so the result depended on which triangle the caller had filled. The same applies toCandRingenpca_cov(). -
constraints_remedy = "clip"now guarantees a PSD result; the previous tolerant fast path could return a matrix with a small negative eigenvalue. - Diagonal weights (vectors or diagonal matrices) are validated identically by every backend: entries within the relative tolerance of zero are set to exactly zero with a message; more negative entries are subject to
constraints_remedy. Previously the eigen path rejected what the other backends silently accepted or clamped. -
method = "eigen"never truncates a metric any more. A positive definite general metric is factored exactly by Cholesky at any size; a singular general metric needs a dense eigendecomposition, which is refused abovemaxeigrows (default now5000) with a message naming the alternatives. The previous behaviour, keeping only themaxeiglargest eigenvectors of the metric before looking atX, could discard the dominant component entirely (a direction with a small metric eigenvalue and large data variance).warn_approxis deprecated and ignored. -
genpca_cov(method = "gmd")requiresCto be PSD within tolerance and no longer clamps negative metric eigenvalues silently. Itstolargument is deprecated in favor ofrank_rtolandmetric_rtol. - New exported
repair_metric()returns a repaired metric together with ametric_repair_report(minimum eigenvalue before and after, Gershgorin bound, applied shift, rank, condition number).
Explicit metric repair (breaking)
-
genpca()now defaults toconstraints_remedy = "error": an indefinite metric stops the fit instead of being silently shifted. Opt into a repair withconstraints_remedy = "ridge","clip"or"identity"; every repair that changes the metric emits a warning of classgenpca_metric_repairedwhosereportfield is therepair_metric()diagnostic.gpca_mle()inherits the new default. -
genpls(),genplsc()andgplssvd_op()gain the sameconstraints_remedyargument (default"error"). Previously their metric operators applied a Gershgorin ridge to any indefinite weight matrix with no message and no way to opt out. -
verbose = TRUEis no longer needed to find out that a metric was replaced by the identity; the warning always fires.
Covariance interface and gpca_mle()
-
genpca_cov()is now the GMD estimator only (eigendecomposition ofR^{1/2} C R^{1/2}). The generalized eigenproblemC v = lambda R v, a different estimator, is the new exportedgeigen_cov().genpca_cov(method = "geigen")still works for one release and forwards with a deprecation warning. -
gpca_mle()defaults toscale_fix = "none". The ridge penalty already identifies the overall scale of the learned metrics, and the previous default"trace"rescaled the metrics after convergence in a way the penalized objective is not invariant to, while still reporting the pre-rescale value.loglikis now always the penalized objective at the returned metrics;loglik_unpenalizedandloglik_rescale_delta(the change caused by a"trace"/"det"rescale) are new.
Solver backend
- The iterative eigen/SVD backend is now the eigencore package; RSpectra is no longer a dependency.
method = "spectra"computes the top-k singular triplets of the metric-whitened dataF_M' X F_A(M = F_M F_M',A = F_A F_A') as an implicit operator: diagonal, dense Cholesky, sparse (CHOLMOD) Cholesky and eigen factors (for singular metrics) are all supported, and a dense SVD is used when the iterative solver does not converge. The C++ Spectra kernel and theLinkingTo: RSpectradependency are gone.gmd_fast_cpp()remains as an alias of the newgmd_spectra(); itsmaxitandseedarguments are ignored. -
genpls(),genplsc()andgplssvd_op()acceptsvd_backend = "eigencore"(new default);"RSpectra"is accepted as a deprecated alias. - Iterative solves never disturb the caller’s
.Random.seed.
genpca 0.1.0
- Initial CRAN submission.
- Generalized PCA with row metric
Mand column metricA(genpca()), following Allen, Grosenick & Taylor (2014). - Covariance-based GPCA from a precomputed
C = X' M X(genpca_cov()), witheigenandgmdpaths. - Multiple computational backends:
eigen,spectra(matrix-free C++ via RSpectra),randomized, anddeflation. Theautoheuristic picks among them. - Generalized PLS / PLS-SVD on two blocks (
genpls(),genplsc()) and an operator-level interface (gplssvd_op()) that avoids materialising whitened matrices. - Sparse functional PCA (
sfpca()), regularised PLS (rpls()), and matrix-normal PCA via maximum residual likelihood (mnpca_mrl()).sfpca()solves each rank-1 subproblem exactly via C++ coordinate descent in the constraint form of Allen & Weylandt (2019), deflates implicitly (sparse inputs never densify), and selects sparsity penalties per component by BIC along a warm-startedlambdapath; smoothness weights default to the scale-free1 / lambda_max(Omega). The formeruthresh/vthreshquantile heuristics are deprecated.sfpca()now returns amultivariousbi_projector(class"sfpca"), soscores(),components(),sdev(), andreconstruct()work as they do forgenpca(). The pre-0.1 list fields$dand$uremain readable but emit a deprecation warning. - Maximum-likelihood metric learning (
gpca_mle()). - SPD constraint handling with
"ridge","clip", and"identity"remediation strategies. - Vignettes covering getting started, metric recipes, scaling, and a reference implementation for GPLSSVD.
Bug fixes and behavior changes
-
gpca_mle()now optimizes a single penalized (MAP) matrix-normal objective with exact block updates (sequential flip-flop covariance updates; the ridgelambdais part of the objective), sologlik_pathis monotone non-decreasing.scale_fixis applied once at exit as a joint, likelihood-preserving rescale (row covariance normalized, factor absorbed into the column covariance) instead of normalizing both factors independently during iteration, and the returnedfitis computed with the returned canonicalized metrics. Reported log-likelihood values now include the penalty term, so they differ numerically from previous versions. -
scores()ongenpcafits now matches Allen, Grosenick & Taylor (2014)’s convention (z_k = X A ov_k = ou_k d_k) and is identical toproject(fit, X)on the training data; previously the two could disagree. -
reconstruct.genpca(fit, colind = )now applies the inverse pre-processing transform to the selected columns, rather than to the firstlength(colind)columns. - Deflation convergence and degeneracy thresholds (the
thresholdargument ofgenpca(method = "deflation")and the internal SFPCA solver) are now relative to the scale of the problem rather than absolute, so results are invariant to rescaling the input data. -
constraints_remedy = "clip"now performs a real spectral clip to the PSD cone (previously it did not clip); it requires a dense eigendecomposition and refuses sparse input larger than 2000 rows/columns, recommending"ridge"instead. - Fixed PSD validation for dense (non-diagonal) constraint matrices so that indefinite input is reliably rejected/repaired rather than silently accepted.
-
genpca(method = "randomized")no longer mutates the caller’s.Random.seed; the C++ kernel seeds its own stream fromseed_randomized, which now fully controls reproducibility. - Added input validation (dimensions, finiteness, penalty/
scad_arange, strictly positive diagonal) to the internal C++ coordinate-descent solver used bysfpca(). - Fixed a cache-key collision in the internal matrix-decomposition cache: the digest previously used rounded matrix entries, which could collide for distinct matrices (e.g. a metric and its ridge-remediated version) and return the wrong cached factor; the digest now hashes exact bytes.
-
genpls()’s$ncompfield now reports the number of components actually extracted, which may be less than the requestedncomp.