Computes the symmetric matrix square root of scale * S after
symmetrising S as (S + t(S)) / 2: with eigendecomposition
\(S = V D V'\), the returned matrix is \(R = V D^{1/2} V'\) (eigenvalues
scaled by scale and clamped at zero before the square root), so that
\(R R' = \mathrm{scale} \cdot S\).
Arguments
- S
Numeric square matrix, a covariance (or any matrix whose symmetrisation
(S + t(S)) / 2is intended; the function symmetrises before decomposing). May be rank-deficient; eigenvalues that are slightly negative from numerical noise are clamped to zero.- scale
Numeric scalar multiplying
Sbefore the root is taken, so that the returned root \(R\) satisfies \(R R' = \mathrm{scale} \cdot S\). Default2, the half-sample covariance convention of the analytic documents.
Value
A symmetric numeric matrix R of the same dimension as
S, satisfying R %*% t(R) equal to scale times the
symmetrised S (up to the zero-clamp on negative eigenvalues).
Why the symmetric root and not V D^(1/2)
Both roots satisfy \(R R' = \mathrm{scale} \cdot S\), but the asymmetric root \(V D^{1/2}\) depends on the eigenvector basis, which is not a continuous function of its input: signs flip and vectors rotate freely inside degenerate subspaces – which a candidate covariance built from complement pairs has exactly, since the complement constraints make it rank-deficient by construction. A pinned RNG seed fixes the standard-normal draws Z; it cannot fix the basis Z is multiplied by, so the asymmetric root is non-reproducible across arithmetically equivalent routes to the same matrix. The symmetric root \(V D^{1/2} V'\) is a matrix function of the symmetrised input alone – basis-free and continuous – so samples built from it are continuous, reproducible functions of the inputs.
Examples
S <- crossprod(matrix(rnorm(25), 5)) # symmetric positive definite
R <- fs_sym_root(S, scale = 2)
max(abs(R %*% t(R) - 2 * S)) # ~ 1e-14
#> [1] 1.776357e-14
isSymmetric(R)
#> [1] TRUE
# Rank-deficient input: the root exists and stays symmetric
X <- matrix(rnorm(15), 5, 3)
R2 <- fs_sym_root(tcrossprod(X), scale = 1)
max(abs(R2 %*% t(R2) - tcrossprod(X)))
#> [1] 7.327472e-15