5

I need to compute an eigendecomposition of an $n\times n$ matrix

$$ D + c vv^\top = Q\Lambda Q^\top \tag{1} $$

in MATLAB, where $D$ is a real diagonal matrix, $c$ is a scalar, and $v$ is a real vector. As is detailed in section 5.3.3 of Demmel's Applied Numerical Linear Algebra, this can be achieved in $\mathcal{O}(n^2)$ operations. As discussed in this book, the numerical implementation of this algorithm is quite subtle, and I would like to leverage a high-quality implementation such as in LAPACK. Unfortunately, even if I am willing to call LAPACK from MATLAB with a MEX file, I am not whether the LAPACK subroutines are written in such a way as to isolate the $\mathcal{O}(n^2)$ computation of (1) that I need. For instance, dlaed1 appears to run in $\mathcal{O}(n^3)$ operation because it is integrated with steps in the divide and conquer eigensolver. This brings me to my question:

Is there a high-quality piece of software for computing (1) in $\mathcal{O}(n^2)$ operations? Can this be called in MATLAB?

eepperly16
  • 380
  • 1
  • 8
  • @BrianBorchers This does not answer my question. The linked article is in the case where $D$ is a dense matrix whose eigendecomposition is known; in my case, $D$ is just a diagonal matrix. This makes the $\mathcal{O}(n^2)$ algorithm suggested in the linked question lead to a speedup for my problem, whereas in that problem the speedup is destroyed by needing to compute the product of two $n\times n$ eigenmatrices. In addition, my question concerns software implementation of the $\mathcal{O}(n^2)$ algorithm, not discussed in the linked question – eepperly16 Jan 31 '23 at 01:03
  • 1
    The point is that your question should be closed and someone should answer the original question by pointing to Demmel's Algorithm 5.3 and discussing how it might be implemented. Looking at it, I see no particular reason to need LAPACK in any of the steps of that algorithm. – Brian Borchers Jan 31 '23 at 01:19
  • Actually, dlaed4() could be used to solve the secular equation to get the eigenvalues of the updated matrix. The remaining two steps of Algorithm 5.3 could then be done with your own code. – Brian Borchers Jan 31 '23 at 01:25
  • And, it looks like dlaed9() will do all the work to update the eigenvalues and Q. – Brian Borchers Jan 31 '23 at 01:29

1 Answers1

7

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 &lt; 3; ++i) {
    if( !mxIsDouble(prhs[0]) || mxIsComplex(prhs[0])) {
      mexErrMsgIdAndTxt( &quot;MATLAB:update_eig:fieldNotRealMatrix&quot;,
              &quot;Input arguments must be a real matrix.&quot;);
    }
}

if (mxGetN(prhs[0]) != 1) {
    mexErrMsgIdAndTxt(&quot;MATLAB:update_eig:dims&quot;,
            &quot;First input must be column vector&quot;);
}

if (mxGetM(prhs[1]) != n || mxGetN(prhs[1]) != 1) {
    mexErrMsgIdAndTxt(&quot;MATLAB:update_eig:dims&quot;,
            &quot;Second input must be column vector, same size as first&quot;);
}


if (mxGetM(prhs[2]) != 1 || mxGetN(prhs[1]) != 1) {
    mexErrMsgIdAndTxt(&quot;MATLAB:update_eig:dims&quot;,
            &quot;Third input must be scalar&quot;);
}

/* 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(&amp;n, &amp;one, &amp;n, &amp;n, l, work, &amp;n, c, d, v, Q, &amp;n, &amp;info);

if (info != 0) {
    if (mxGetM(prhs[1]) != n || mxGetN(prhs[1]) != 1) {
        mexWarnMsgIdAndTxt(&quot;MATLAB:update_eig:dslaed9_error&quot;,
                &quot;dlaed9 exited with error %d&quot;, info);
    }
}

bool found_nans = false;
for (int i = 0; i &lt; n; ++i){
    if (isnan(l[i])) {
        mexWarnMsgIdAndTxt(&quot;MATLAB:update_eig:nans&quot;,
            &quot;d vector has NaNs, problem not properly deflated&quot;);
        break;
    }
    for (int j = 0; j &lt; n; ++j) {
        if (isnan(Q[i + n*j])) {
            mexWarnMsgIdAndTxt(&quot;MATLAB:update_eig:nans&quot;,
                &quot;Q matrix has NaNs, problem not properly deflated&quot;);
            found_nans = true;
            break;
        }
    }
    if (found_nans) break;
}

free(work);

}

Confirming $\mathcal{O}(n^2)$ runtime:

enter image description here

eepperly16
  • 380
  • 1
  • 8
  • 1
    dlaed9 has its limitations as it is meant for a very specific purpose. I am currently working on a C++ based suite of eigensolvers which, among other special matrices, supports the DPR1 case. Once a beta version is ready I will add a comment with a link to that. – Marcel Ferrari Sep 15 '23 at 08:54
  • @MarcelFerrari I fully agree. Looking forward to the beta – eepperly16 Sep 16 '23 at 15:23
  • 1
    The code is still super messy and early in development, but it runs pretty fast and generates correct results now https://github.com/MarcelFerrari/Freccia/blob/main/src/DPR1/DPR1Solver.cpp

    I've done 10kx10k DPR1 matrix in ~800 ms using MKL backend and running in parallel.

    – Marcel Ferrari Sep 20 '23 at 15:52