Inconsistent results with complex matrices
eliocamp opened this issue · 2 comments
I'm using {irlba} to perform svd on complex matrices and found that the results where inconsistent. In my real code, I had an issue that the resulting principal values depended on the order of the rows. Trying to create a reproducible example, I realised that the solution was inconsistent between different calls even if the matrix remained the same
set.seed(42)
N <- 10
M <- 40
m <- matrix(complex(real = rnorm(M*N), imaginary = rnorm(M*N)),
ncol = N, nrow = M)
first_pc <- function(matrix, package) {
if (package == "svd") {
s <- svd(matrix, nu = 2)
} else {
s <- suppressWarnings(irlba::irlba(matrix, nu = 2, rng = runif) )
}
s$v[, 1]
}
Plotting the first of several calls to irlba::irlba
, the results change considerably!
plot(first_pc(m, "irlba"), type = "l")
lines(first_pc(m, "irlba"))
lines(first_pc(m, "irlba"))
lines(first_pc(m, "irlba"))
Using base::svd
, the results are always the same:
plot(-first_pc(m, "svd"), type = "l")
lines(-first_pc(m, "svd"))
lines(-first_pc(m, "svd"))
lines(-first_pc(m, "svd"))
With real values, this doesn't happen:
m <- matrix(rnorm(M*N),
ncol = N, nrow = M)
plot(first_pc(m, "irlba"), type = "l")
lines(first_pc(m, "irlba"))
lines(first_pc(m, "irlba"))
lines(first_pc(m, "irlba"))
Created on 2020-11-02 by the reprex package (v0.3.0)
Hi, I've been having issues with this for a while and I've just now realised that this is not necessarily a bug. It's just that complex SVDs are undefined up to a rotation angle!
set.seed(42)
N <- 10
M <- 40
m <- matrix(complex(real = rnorm(M*N), imaginary = rnorm(M*N)),
ncol = N, nrow = M)
# Baseline svd
base <- irlba::irlba(m, 2)
rotate <- function(z, angle = 0) {
complex(real = cos(angle), imaginary = sin(angle)) * z
}
first_pc <- function(matrix, package) {
if (package == "svd") {
s <- svd(matrix, nu = 2)
} else {
s <- suppressWarnings(irlba::irlba(matrix, nu = 2))
}
# Rotate v so that is has the maximum corraltion with
# the baseline value.
angles <- seq(0, 2*pi, length.out = 80)
cors <- vapply(angles, function(angle) {
v <- rotate(s$v[, 1], angle)
cor(Re(v), Re(base$v[, 1]))
}, FUN.VALUE = 1)
rotate(s$v[, 1], angles[which.max(cors)])
}
plot(first_pc(m, "irlba"), type = "l")
lines(first_pc(m, "irlba"))
lines(first_pc(m, "irlba"))
lines(first_pc(m, "irlba"))
Created on 2021-12-07 by the reprex package (v2.0.1)
I don't know if there's anything that can make the algorithm more deterministic, in that it returns always the same svd decomposition for the same input (like base::svd()
does), but in any case, this is just a property of complex SVD; it's just not unique. ¯_(ツ)_/¯