Thanks to Brian Borchers for suggesting dlaed9
; this mostly solves my problem.
Unfortunately, dlaed9
does not include deflation if $D$ has repeated entries or $v$ has zero entries, in which case dlaed9
will either produce an error message or populate the output eigenvalues/eigenvectors.
To help anyone who may follow down the same path I did, here is a MEX file which calls dlaed9
. I have not made an effort to implement deflation, but I have written checks for errors so this issue should at least not fail silently.
MATLAB file update_eig.m
function [Q,D] = update_eig(d,v,c)
%% Negate, sort, and normalize
negative = c < 0;
if negative; d = -d; c = -c; end
[d,p] = sort(d,'ascend');
v = v(p);
c = c * norm(v)^2;
v = v / norm(v);
%% Compute update
[Q,D] = update_eig_mex(d,v,c);
%% Undo negation and sorting
Q(p,:) = Q;
if negative; D = -D; end
D = diag(D);
end
MEX file update_eig_mex.c
// Compute eigendecomposition of diag(d) + c*v*v', for a unit vector v
#include "mex.h"
#include "lapack.h"
#include "math.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
#if MX_HAS_INTERLEAVED_COMPLEX
mxDouble *d, *v, *c, *Q, *l;
#else
double *d, *v, *c, *Q, *l;
#endif
ptrdiff_t n; /* matrix dimensions */
#if MX_HAS_INTERLEAVED_COMPLEX
d = mxGetDoubles(prhs[0]);
v = mxGetDoubles(prhs[1]);
c = mxGetDoubles(prhs[2]);
#else
d = mxGetPr(prhs[0]);
v = mxGetPr(prhs[1]);
c = mxGetPr(prhs[2]);
#endif
n = mxGetM(prhs[0]);
for (int i = 0; i < 3; ++i) {
if( !mxIsDouble(prhs[0]) || mxIsComplex(prhs[0])) {
mexErrMsgIdAndTxt( "MATLAB:update_eig:fieldNotRealMatrix",
"Input arguments must be a real matrix.");
}
}
if (mxGetN(prhs[0]) != 1) {
mexErrMsgIdAndTxt("MATLAB:update_eig:dims",
"First input must be column vector");
}
if (mxGetM(prhs[1]) != n || mxGetN(prhs[1]) != 1) {
mexErrMsgIdAndTxt("MATLAB:update_eig:dims",
"Second input must be column vector, same size as first");
}
if (mxGetM(prhs[2]) != 1 || mxGetN(prhs[1]) != 1) {
mexErrMsgIdAndTxt("MATLAB:update_eig:dims",
"Third input must be scalar");
}
/* create output matrix C */
plhs[0] = mxCreateDoubleMatrix(n, n, mxREAL);
plhs[1] = mxCreateDoubleMatrix(n, 1, mxREAL);;
#if MX_HAS_INTERLEAVED_COMPLEX
Q = mxGetDoubles(plhs[0]);
l = mxGetDoubles(plhs[1]);
#else
Q = mxGetPr(plhs[0]);
l = mxGetPr(plhs[1]);
#endif
double *work = malloc(sizeof(double) * n*n);
ptrdiff_t info;
ptrdiff_t one = 1;
/* Pass arguments to Fortran by reference */
dlaed9(&n, &one, &n, &n, l, work, &n, c, d, v, Q, &n, &info);
if (info != 0) {
if (mxGetM(prhs[1]) != n || mxGetN(prhs[1]) != 1) {
mexWarnMsgIdAndTxt("MATLAB:update_eig:dslaed9_error",
"dlaed9 exited with error %d", info);
}
}
bool found_nans = false;
for (int i = 0; i < n; ++i){
if (isnan(l[i])) {
mexWarnMsgIdAndTxt("MATLAB:update_eig:nans",
"d vector has NaNs, problem not properly deflated");
break;
}
for (int j = 0; j < n; ++j) {
if (isnan(Q[i + n*j])) {
mexWarnMsgIdAndTxt("MATLAB:update_eig:nans",
"Q matrix has NaNs, problem not properly deflated");
found_nans = true;
break;
}
}
if (found_nans) break;
}
free(work);
}
Confirming $\mathcal{O}(n^2)$ runtime: