PDSTEIN(l)

πŸ“¦ version 1.5, in ubuntu jammy

πŸ’– PDSTEIN(l) β€” LAPACK routine, version 1.5 πŸ’–

okay bestie, sit DOWN, because this one is a whole journey ✨ It's the PDSTEIN page from the LAPACK routine docs, and it's lowkey iconic. Manual name? PDSTEIN, section l. That's the one. Periodt.


🌸 NAME 🌸

PDSTEIN β€” compute the eigenvectors of a symmetric tridiagonal matrix in parallel, using inverse iteration πŸ’…


✨ SYNOPSIS ✨

SUBROUTINE PDSTEIN( N, D, E, M, W, IBLOCK, ISPLIT, ORFAC, Z, IZ, JZ,
                    DESCZ, WORK, LWORK, IWORK, LIWORK, IFAIL, ICLUSTR,
                    GAP, INFO )

    INTEGER         INFO, IZ, JZ, LIWORK, LWORK, M, N

    DOUBLE          PRECISION ORFAC

    INTEGER         DESCZ( * ), IBLOCK( * ), ICLUSTR( * ), IFAIL( * ),
                    ISPLIT( * ), IWORK( * )

    DOUBLE          PRECISION D( * ), E( * ), GAP( * ), W( * ), WORK( *
                    ), Z( * )

yes the line wrapping in the original is exactly that chaotic, and we are keeping it, no cap 🫢


πŸ¦‹ PURPOSE πŸ¦‹

PDSTEIN computes the eigenvectors of a symmetric tridiagonal matrix in parallel, using inverse iteration. The eigenvectors it finds correspond to user specified eigenvalues. ✨

Okay but here's the tea, bestie, and you need to hear it: PDSTEIN does not orthogonalize vectors that are on different processes. The extent of orthogonalization is controlled by the input parameter LWORK. Eigenvectors that are to be orthogonalized are computed by the same process. PDSTEIN decides on the allocation of work among the processes and then calls DSTEIN2 (a modified LAPACK routine) on each individual process. If insufficient workspace is allocated, the expected orthogonalization may not be done. 😭

Note: If the eigenvectors obtained are not orthogonal, increase LWORK and run the code again. (fr fr, this is the move.)

πŸ“ Notes πŸ“

Each global data object is described by an associated description vector. This vector stores the information required to establish the mapping between an object element and its corresponding process and memory location. πŸ’«

Let A be a generic term for any 2D block cyclicly distributed array. Such a global array has an associated description vector DESCA. In the following comments, the character _ should be read as "of the global array". 🧚

  • DTYPE_A (global) β€” stored in DESCA( DTYPE_ ) β€” The descriptor type. In this case, DTYPE_A = 1.
  • CTXT_A (global) β€” stored in DESCA( CTXT_ ) β€” The BLACS context handle, indicating the BLACS process grid A is distributed over. The context itself is global, but the handle (the integer value) may vary.
  • M_A (global) β€” stored in DESCA( M_ ) β€” The number of rows in the global array A.
  • N_A (global) β€” stored in DESCA( N_ ) β€” The number of columns in the global array A.
  • MB_A (global) β€” stored in DESCA( MB_ ) β€” The blocking factor used to distribute the rows of the array.
  • NB_A (global) β€” stored in DESCA( NB_ ) β€” The blocking factor used to distribute the columns of the array.
  • RSRC_A (global) β€” stored in DESCA( RSRC_ ) β€” The process row over which the first row of the array A is distributed.
  • CSRC_A (global) β€” stored in DESCA( CSRC_ ) β€” The process column over which the first column of the array A is distributed.
  • LLD_A (local) β€” stored in DESCA( LLD_ ) β€” The leading dimension of the local array. LLD_A >= MAX(1,LOCr(M_A)).

so many letters, I know, but stay with me, it's giving very much "organized" πŸ’œ


Let K be the number of rows or columns of a distributed matrix, and assume that its process grid has dimension p x q. 🌈

LOCr( K ) denotes the number of elements of K that a process would receive if K were distributed over the p processes of its process column. ✨

Similarly, LOCc( K ) denotes the number of elements of K that a process would receive if K were distributed over the q processes of its process row. ✨

The values of LOCr() and LOCc() may be determined via a call to the ScaLAPACK tool function, NUMROC:

LOCr( M ) = NUMROC( M, MB_A, MYROW, RSRC_A, NPROW ),
LOCc( N ) = NUMROC( N, NB_A, MYCOL, CSRC_A, NPCOL ).

An upper bound for these quantities may be computed by:

LOCr( M ) <= ceil( ceil(M/MB_A)/NPROW )*MB_A
LOCc( N ) <= ceil( ceil(N/NB_A)/NPCOL )*NB_A

math but make it cute πŸ’…βœ¨πŸŒˆ


and that's the first half, bestie β€” do NOT close the tab, there's more coming πŸ‘‘

πŸŽ€ ARGUMENTS πŸŽ€

ok so first things first bestie, the lil legend: P = NPROW * NPCOL is the total number of processes. That's the squad size, remember it, it comes up everywhere. πŸ’…


  • N (global input) INTEGER β€” the order of the tridiagonal matrix T. And N >= 0, no negatives, she doesn't play like that. ✨
  • D (global input) DOUBLE PRECISION array, dimension (N) β€” the n diagonal elements of the tridiagonal matrix T. The main character elements, honestly. πŸ‘‘
  • E (global input) DOUBLE PRECISION array, dimension (N-1) β€” the (n-1) off-diagonal elements of the tridiagonal matrix T. The supporting cast, still essential. 🫢
  • M (global input) INTEGER β€” the total number of eigenvectors we're trying to find. 0 <= M <= N, no cap (well, actually cap at N, but you get me). πŸ’–
  • W (global input/global output) DOUBLE PRECISION array, dim (M) β€” okay so on input, the first M elements of W hold all the eigenvalues you want eigenvectors for. They need to be grouped by split-off block and ordered smallest to largest within each block. (What's expected here is the output array W from PDSTEBZ with ORDER='b', so like, go get that first.) This array should be replicated on all processes, fr fr. On output, the first M elements contain the input eigenvalues in ascending order. Sorted, organized, slay. ✨
Note: to get orthogonal vectors, it's best if eigenvalues are computed to the highest accuracy possible β€” you do that by setting ABSTOL to the underflow threshold = DLAMCH('U'). (ABSTOL is an input parameter to PDSTEBZ, iykyk.) 🌸
  • IBLOCK (global input) INTEGER array, dimension (N) β€” the submatrix indices that go with the corresponding eigenvalues in W. So 1 = eigenvalue belongs to the first submatrix from the top, 2 = second submatrix, etc. (Again, the output array IBLOCK from PDSTEBZ is what's expected here.) πŸ¦‹
  • ISPLIT (global input) INTEGER array, dimension (N) β€” the splitting points, aka where T breaks up into submatrices. So: the first submatrix is rows/columns 1 to ISPLIT(1), the second is rows/columns ISPLIT(1)+1 through ISPLIT(2), etc., and the NSPLIT-th is rows/columns ISPLIT(NSPLIT-1)+1 through ISPLIT(NSPLIT)=N. (And yes, you guessed it, the output array ISPLIT from PDSTEBZ is expected here.) πŸ”₯
  • ORFAC (global input) DOUBLE PRECISION β€” ORFAC decides which eigenvectors get orthogonalized. Eigenvectors whose eigenvalues are within ORFAC*||T|| of each other get the orthogonalization treatment. BUT β€” and this is important β€” if the workspace isn't enough (see LWORK), this tolerance may get decreased until all the eigenvectors that need orthogonalizing can actually fit in one process. If ORFAC equals zero, no orthogonalization happens at all, periodt. And if ORFAC is negative, a default value of 10^-3 is used. ORFAC should be identical on all processes, no chaos. πŸ’…
  • Z (local output) DOUBLE PRECISION array, dimension (DESCZ(DLEN_), N/npcol + NB) β€” Z contains the computed eigenvectors that go with the specified eigenvalues. Any vector that fails to converge is set to its current iterate after MAXITS iterations (see DSTEIN2). On output, Z is distributed across the P processes in block cyclic format. 🌈
  • IZ (global input) INTEGER β€” Z's global row index, which points to the beginning of the submatrix that's about to be operated on. πŸ“
  • JZ (global input) INTEGER β€” Z's global column index, which points to the beginning of the submatrix that's about to be operated on. πŸ“
  • DESCZ (global and local input) INTEGER array of dimension DLEN_ β€” the array descriptor for the distributed matrix Z. Her ID card, basically. πŸŽ€
  • WORK (local workspace/global output) DOUBLE PRECISION array, dimension ( LWORK ) β€” on output, WORK(1) gives a lower bound on the workspace (LWORK) that guarantees the orthogonalization you asked for (see ORFAC). Just so you know: this may overestimate the minimum workspace actually needed. πŸ€·β™€οΈ
  • LWORK (local input) integer β€” LWORK controls how much orthogonalization can be done. The number of eigenvectors with storage allocated on each process is:
NVEC = floor(( LWORK- max(5*N,NP00*MQ00) )/N)

Eigenvectors corresponding to eigenvalue clusters of size NVEC - ceil(M/P) + 1 are guaranteed to be orthogonal (this orthogonality is similar to what you get from DSTEIN2). πŸ’–

Note: LWORK must be no smaller than max(5*N,NP00*MQ00) + ceil(M/P)*N, and it should have the same input value on all processes. It's the minimum value of LWORK input on different processes that's significant, so if one process cheaps out, that's the vibe for everyone.

If LWORK = -1, then LWORK is global input and a workspace query is assumed. The routine only calculates the minimum and optimal size for all work arrays. Each of these values is returned in the first entry of the corresponding work array, and no error message is issued by PXERBLA. So it's a lil "just tell me the sizes" moment, no drama. ✨

  • IWORK (local workspace/global output) INTEGER array, dimension ( 3N+P+1 )* β€” on return, IWORK(1) contains the amount of integer workspace required. Also on return, IWORK(2) through IWORK(P+2) indicate the eigenvectors computed by each process. Process I computes eigenvectors indexed IWORK(I+2)+1 thru' IWORK(I+3). Receipts, basically. 🧾
  • LIWORK (local input) INTEGER β€” size of array IWORK. Must be >= 3*N + P + 1.

If LIWORK = -1, then LIWORK is global input and a workspace query is assumed. The routine only calculates the minimum and optimal size for all work arrays. Each of these values is returned in the first entry of the corresponding work array, and no error message is issued by PXERBLA. Same energy as the LWORK one. πŸ’…

  • IFAIL (global output) integer array, dimension (M) β€” on a normal exit, every element of IFAIL is zero. If one or more eigenvectors fail to converge after MAXITS iterations (just like in DSTEIN), then INFO > 0 gets returned. And if mod(INFO,M+1)>0, then for I=1 to mod(INFO,M+1), the eigenvector corresponding to eigenvalue W(IFAIL(I)) failed to converge (W here means the array of eigenvalues on output). 😭
  • ICLUSTR (global output) integer array, dimension (2P)* β€” this output array holds indices of eigenvectors corresponding to a cluster of eigenvalues that could not be orthogonalized because the workspace wasn't enough (see LWORK, ORFAC and INFO). Eigenvectors corresponding to clusters of eigenvalues indexed ICLUSTR(2*I-1) to ICLUSTR(2*I), I = 1 to INFO/(M+1), couldn't be orthogonalized due to lack of workspace. So yeah, the eigenvectors for those clusters may not be orthogonal, that's the tea. ICLUSTR is a zero terminated array β€” ( ICLUSTR(2*K).NE.0 .AND. ICLUSTR(2*K+1).EQ.0 ) if and only if K is the number of clusters. 🫠
  • GAP (global output) DOUBLE PRECISION array, dimension (P) β€” this output array holds the gap between eigenvalues whose eigenvectors couldn't be orthogonalized. The INFO/M output values in this array correspond to the INFO/(M+1) clusters indicated by the array ICLUSTR. As a result, the dot product between eigenvectors corresponding to the I^th cluster may be as high as ( O(n)*macheps ) / GAP(I). πŸ‘€
  • INFO (global output) INTEGER β€”
    • = 0: successful exit. we love to see it. πŸŽ‰
    • < 0: if the i-th argument is an array and the j-entry had an illegal value, then INFO = -(i*100+j); if the i-th argument is a scalar and had an illegal value, then INFO = -i. Also: < 0 β€” if INFO = -I, the I-th argument had an illegal value. πŸ’€
    • > 0: if mod(INFO,M+1) = I, then I eigenvectors failed to converge in MAXITS iterations. Their indices are stored in the array IFAIL. And if INFO/(M+1) = I, then eigenvectors corresponding to I clusters of eigenvalues could not be orthogonalized due to insufficient workspace. The indices of those clusters are stored in the array ICLUSTR. πŸ™ˆ

ok that's the whole page bestie, you're officially a PDSTEIN girlie now πŸ’– this was LAPACK version 1.5, dated 12 May 1997, and the page is PDSTEIN(l). go forth and orthogonalize responsibly, ily, bye!! πŸŽ€βœ¨πŸ¦‹

πŸ“¦ where this page comes from