Date: 2026-07-30T21:14:12+00:00

Actual source code: spectral.c

      1: #include <petscmat.h>
      2: #include <petscblaslapack.h>

      4: /*@
      5:   MatCreateLaplacian - Create the matrix Laplacian, with all values in the matrix less than the tolerance set to zero

      7:   Input Parameters:
      8: + A        - The matrix
      9: . tol      - The zero tolerance
     10: - weighted - Flag for using edge weights

     12:   Output Parameter:
     13: . L - The graph Laplacian matrix

     15:   Level: intermediate

     17: .seealso: `MatFilter()`, `MatGetGraph()`
     18:  @*/
     19: PetscErrorCode MatCreateLaplacian(Mat A, PetscReal tol, PetscBool weighted, Mat *L)
     20: {
     21:   PetscScalar *newVals;
     22:   PetscInt    *newCols;
     23:   PetscInt     rStart, rEnd, r, colMax = 0;
     24:   PetscInt    *dnnz, *onnz;
     25:   PetscInt     m, n, M, N;

     27:   PetscFunctionBegin;
     28:   PetscCheck(!weighted, PetscObjectComm((PetscObject)A), PETSC_ERR_SUP, "Will get to this soon");
     29:   PetscCall(MatCreate(PetscObjectComm((PetscObject)A), L));
     30:   PetscCall(MatGetSize(A, &M, &N));
     31:   PetscCall(MatGetLocalSize(A, &m, &n));
     32:   PetscCall(MatSetSizes(*L, m, n, M, N));
     33:   PetscCall(MatGetOwnershipRange(A, &rStart, &rEnd));
     34:   PetscCall(PetscMalloc2(m, &dnnz, m, &onnz));
     35:   for (r = rStart; r < rEnd; ++r) {
     36:     const PetscScalar *vals;
     37:     const PetscInt    *cols;
     38:     PetscInt           ncols, newcols, c;
     39:     PetscBool          hasdiag = PETSC_FALSE;

     41:     dnnz[r - rStart] = onnz[r - rStart] = 0;
     42:     PetscCall(MatGetRow(A, r, &ncols, &cols, &vals));
     43:     for (c = 0, newcols = 0; c < ncols; ++c) {
     44:       if (cols[c] == r) {
     45:         ++newcols;
     46:         hasdiag = PETSC_TRUE;
     47:         ++dnnz[r - rStart];
     48:       } else if (PetscAbsScalar(vals[c]) >= tol) {
     49:         if ((cols[c] >= rStart) && (cols[c] < rEnd)) ++dnnz[r - rStart];
     50:         else ++onnz[r - rStart];
     51:         ++newcols;
     52:       }
     53:     }
     54:     if (!hasdiag) {
     55:       ++newcols;
     56:       ++dnnz[r - rStart];
     57:     }
     58:     colMax = PetscMax(colMax, newcols);
     59:     PetscCall(MatRestoreRow(A, r, &ncols, &cols, &vals));
     60:   }
     61:   PetscCall(MatSetFromOptions(*L));
     62:   PetscCall(MatXAIJSetPreallocation(*L, 1, dnnz, onnz, NULL, NULL));
     63:   PetscCall(MatSetUp(*L));
     64:   PetscCall(PetscMalloc2(colMax, &newCols, colMax, &newVals));
     65:   for (r = rStart; r < rEnd; ++r) {
     66:     const PetscScalar *vals;
     67:     const PetscInt    *cols;
     68:     PetscInt           ncols, newcols, c;
     69:     PetscBool          hasdiag = PETSC_FALSE;

     71:     PetscCall(MatGetRow(A, r, &ncols, &cols, &vals));
     72:     for (c = 0, newcols = 0; c < ncols; ++c) {
     73:       if (cols[c] == r) {
     74:         newCols[newcols] = cols[c];
     75:         newVals[newcols] = dnnz[r - rStart] + onnz[r - rStart] - 1;
     76:         ++newcols;
     77:         hasdiag = PETSC_TRUE;
     78:       } else if (PetscAbsScalar(vals[c]) >= tol) {
     79:         newCols[newcols] = cols[c];
     80:         newVals[newcols] = -1.0;
     81:         ++newcols;
     82:       }
     83:       PetscCheck(newcols <= colMax, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Overran work space");
     84:     }
     85:     if (!hasdiag) {
     86:       newCols[newcols] = r;
     87:       newVals[newcols] = dnnz[r - rStart] + onnz[r - rStart] - 1;
     88:       ++newcols;
     89:     }
     90:     PetscCall(MatRestoreRow(A, r, &ncols, &cols, &vals));
     91:     PetscCall(MatSetValues(*L, 1, &r, newcols, newCols, newVals, INSERT_VALUES));
     92:   }
     93:   PetscCall(PetscFree2(dnnz, onnz));
     94:   PetscCall(MatAssemblyBegin(*L, MAT_FINAL_ASSEMBLY));
     95:   PetscCall(MatAssemblyEnd(*L, MAT_FINAL_ASSEMBLY));
     96:   PetscCall(PetscFree2(newCols, newVals));
     97:   PetscFunctionReturn(PETSC_SUCCESS);
     98: }

    100: /*
    101:   MatGetOrdering_Spectral - Find the symmetric reordering of the graph by .
    102: */
    103: PETSC_INTERN PetscErrorCode MatGetOrdering_Spectral(Mat A, MatOrderingType type, IS *row, IS *col)
    104: {
    105:   Mat             L;
    106:   const PetscReal eps = 1.0e-12;

    108:   PetscFunctionBegin;
    109:   PetscCall(MatCreateLaplacian(A, eps, PETSC_FALSE, &L));
    110:   {
    111:     /* Check Laplacian */
    112:     PetscReal norm;
    113:     Vec       x, y;

    115:     PetscCall(MatCreateVecs(L, &x, NULL));
    116:     PetscCall(VecDuplicate(x, &y));
    117:     PetscCall(VecSet(x, 1.0));
    118:     PetscCall(MatMult(L, x, y));
    119:     PetscCall(VecNorm(y, NORM_INFINITY, &norm));
    120:     PetscCheck(norm <= 1.0e-10, PetscObjectComm((PetscObject)y), PETSC_ERR_PLIB, "Invalid graph Laplacian");
    121:     PetscCall(VecDestroy(&x));
    122:     PetscCall(VecDestroy(&y));
    123:   }
    124:   /* Compute Fiedler vector (right now, all eigenvectors) */
    125: #if defined(PETSC_USE_COMPLEX)
    126:   SETERRQ(PetscObjectComm((PetscObject)A), PETSC_ERR_SUP, "Spectral partitioning does not support complex numbers");
    127: #else
    128:   {
    129:     Mat          LD;
    130:     PetscScalar *a;
    131:     PetscReal   *realpart, *imagpart, *eigvec, *work;
    132:     PetscReal    sdummy;
    133:     PetscBLASInt bn, bN, lwork = 0, lierr, idummy;
    134:     PetscInt     n, i, evInd, *perm, tmp;

    136:     PetscCall(MatConvert(L, MATDENSE, MAT_INITIAL_MATRIX, &LD));
    137:     PetscCall(MatGetLocalSize(LD, &n, NULL));
    138:     PetscCall(MatDenseGetArray(LD, &a));
    139:     PetscCall(PetscBLASIntCast(n, &bn));
    140:     PetscCall(PetscBLASIntCast(n, &bN));
    141:     PetscCall(PetscBLASIntCast(5 * n, &lwork));
    142:     PetscCall(PetscBLASIntCast(1, &idummy));
    143:     PetscCall(PetscMalloc4(n, &realpart, n, &imagpart, n * n, &eigvec, lwork, &work));
    144:     PetscCall(PetscFPTrapPush(PETSC_FP_TRAP_OFF));
    145:     PetscCallBLAS("LAPACKgeev", LAPACKgeev_("N", "V", &bn, a, &bN, realpart, imagpart, &sdummy, &idummy, eigvec, &bN, work, &lwork, &lierr));
    146:     PetscCheck(!lierr, PETSC_COMM_SELF, PETSC_ERR_LIB, "Error in LAPACK routine %" PetscBLASInt_FMT, lierr);
    147:     PetscCall(PetscFPTrapPop());
    148:     PetscCall(MatDenseRestoreArray(LD, &a));
    149:     PetscCall(MatDestroy(&LD));
    150:     /* Check lowest eigenvalue and eigenvector */
    151:     PetscCall(PetscMalloc1(n, &perm));
    152:     for (i = 0; i < n; ++i) perm[i] = i;
    153:     PetscCall(PetscSortRealWithPermutation(n, realpart, perm));
    154:     evInd = perm[0];
    155:     PetscCheck(realpart[evInd] <= 1.0e-12 && imagpart[evInd] <= 1.0e-12, PetscObjectComm((PetscObject)L), PETSC_ERR_PLIB, "Graph Laplacian must have lowest eigenvalue 0");
    156:     evInd = perm[1];
    157:     PetscCheck(realpart[evInd] >= 1.0e-12 || imagpart[evInd] >= 1.0e-12, PetscObjectComm((PetscObject)L), PETSC_ERR_PLIB, "Graph Laplacian must have only one zero eigenvalue");
    158:     evInd = perm[0];
    159:     for (i = 0; i < n; ++i) {
    160:       PetscCheck(PetscAbsReal(eigvec[evInd * n + i] - eigvec[evInd * n + 0]) <= 1.0e-10, PetscObjectComm((PetscObject)L), PETSC_ERR_PLIB, "Graph Laplacian must have constant lowest eigenvector ev_%" PetscInt_FMT " %g != ev_0 %g", i, (double)eigvec[evInd * n + i], (double)eigvec[evInd * n + 0]);
    161:     }
    162:     /* Construct Fiedler partition */
    163:     evInd = perm[1];
    164:     for (i = 0; i < n; ++i) perm[i] = i;
    165:     PetscCall(PetscSortRealWithPermutation(n, &eigvec[evInd * n], perm));
    166:     for (i = 0; i < n / 2; ++i) {
    167:       tmp             = perm[n - 1 - i];
    168:       perm[n - 1 - i] = perm[i];
    169:       perm[i]         = tmp;
    170:     }
    171:     PetscCall(ISCreateGeneral(PETSC_COMM_SELF, n, perm, PETSC_OWN_POINTER, row));
    172:     PetscCall(PetscObjectReference((PetscObject)*row));
    173:     *col = *row;

    175:     PetscCall(PetscFree4(realpart, imagpart, eigvec, work));
    176:     PetscCall(MatDestroy(&L));
    177:     PetscFunctionReturn(PETSC_SUCCESS);
    178:   }
    179: #endif
    180: }