Date: 2026-01-20T11:35:46+00:00

Actual source code: lmesolve.c

      1: /*
      2:    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      3:    SLEPc - Scalable Library for Eigenvalue Problem Computations
      4:    Copyright (c) 2002-, Universitat Politecnica de Valencia, Spain

      6:    This file is part of SLEPc.
      7:    SLEPc is distributed under a 2-clause BSD license (see LICENSE).
      8:    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      9: */
     10: /*
     11:    LME routines related to the solution process
     12: */

     14: #include <slepc/private/lmeimpl.h>
     15: #include <slepcblaslapack.h>

     17: /*@
     18:    LMESolve - Solves the linear matrix equation.

     20:    Collective

     22:    Input Parameter:
     23: .  lme - the linear matrix equation solver context

     25:    Options Database Keys:
     26: +  -lme_view             - print information about the solver once the solve is complete
     27: .  -lme_view_pre         - print information about the solver before the solve starts
     28: .  -lme_view_mat_a       - view the A matrix
     29: .  -lme_view_mat_b       - view the B matrix
     30: .  -lme_view_mat_d       - view the D matrix
     31: .  -lme_view_mat_e       - view the E matrix
     32: .  -lme_view_rhs         - view the right hand side of the equation
     33: .  -lme_view_solution    - view the computed solution
     34: -  -lme_converged_reason - print reason for convergence/divergence, and number of iterations

     36:    Notes:
     37:    The matrix coefficients are specified with `LMESetCoefficients()`. The right-hand
     38:    side is specified with `LMESetRHS()`. The placeholder for the solution is specified
     39:    with `LMESetSolution()`.

     41:    All the command-line options listed above admit an optional argument specifying
     42:    the viewer type and options. For instance, use `-lme_view_mat_a binary:amatrix.bin`
     43:    to save the $A$ matrix to a binary file.

     45:    Level: beginner

     47: .seealso: [](ch:lme), `LMECreate()`, `LMESetUp()`, `LMEDestroy()`, `LMESetTolerances()`, `LMESetCoefficients()`, `LMESetRHS()`, `LMESetSolution()`
     48: @*/
     49: PetscErrorCode LMESolve(LME lme)
     50: {
     51:   PetscFunctionBegin;

     54:   /* call setup */
     55:   PetscCall(LMESetUp(lme));
     56:   lme->its    = 0;
     57:   lme->errest = 0.0;

     59:   PetscCall(LMEViewFromOptions(lme,NULL,"-lme_view_pre"));

     61:   /* call solver */
     62:   PetscCheck(lme->ops->solve[lme->problem_type],PetscObjectComm((PetscObject)lme),PETSC_ERR_SUP,"The specified solver does not support equation type %s",LMEProblemTypes[lme->problem_type]);
     63:   PetscCall(PetscLogEventBegin(LME_Solve,lme,0,0,0));
     64:   PetscUseTypeMethod(lme,solve[lme->problem_type]);
     65:   PetscCall(PetscLogEventEnd(LME_Solve,lme,0,0,0));

     67:   PetscCheck(lme->reason,PetscObjectComm((PetscObject)lme),PETSC_ERR_PLIB,"Internal error, solver returned without setting converged reason");

     69:   PetscCheck(!lme->errorifnotconverged || lme->reason>=0,PetscObjectComm((PetscObject)lme),PETSC_ERR_NOT_CONVERGED,"LMESolve has not converged");

     71:   /* various viewers */
     72:   PetscCall(LMEViewFromOptions(lme,NULL,"-lme_view"));
     73:   PetscCall(LMEConvergedReasonViewFromOptions(lme));
     74:   PetscCall(MatViewFromOptions(lme->A,(PetscObject)lme,"-lme_view_mat_a"));
     75:   if (lme->B) PetscCall(MatViewFromOptions(lme->B,(PetscObject)lme,"-lme_view_mat_b"));
     76:   if (lme->D) PetscCall(MatViewFromOptions(lme->D,(PetscObject)lme,"-lme_view_mat_d"));
     77:   if (lme->E) PetscCall(MatViewFromOptions(lme->E,(PetscObject)lme,"-lme_view_mat_e"));
     78:   PetscCall(MatViewFromOptions(lme->C,(PetscObject)lme,"-lme_view_rhs"));
     79:   PetscCall(MatViewFromOptions(lme->X,(PetscObject)lme,"-lme_view_solution"));
     80:   PetscFunctionReturn(PETSC_SUCCESS);
     81: }

     83: /*@
     84:    LMEGetIterationNumber - Gets the current iteration number. If the
     85:    call to `LMESolve()` is complete, then it returns the number of iterations
     86:    carried out by the solution method.

     88:    Not Collective

     90:    Input Parameter:
     91: .  lme - the linear matrix equation solver context

     93:    Output Parameter:
     94: .  its - number of iterations

     96:    Note:
     97:    During the $i$-th iteration this call returns $i-1$. If `LMESolve()` is
     98:    complete, then parameter `its` contains either the iteration number at
     99:    which convergence was successfully reached, or failure was detected.
    100:    Call `LMEGetConvergedReason()` to determine if the solver converged or
    101:    failed and why.

    103:    Level: intermediate

    105: .seealso: [](ch:lme), `LMEGetConvergedReason()`, `LMESetTolerances()`
    106: @*/
    107: PetscErrorCode LMEGetIterationNumber(LME lme,PetscInt *its)
    108: {
    109:   PetscFunctionBegin;
    111:   PetscAssertPointer(its,2);
    112:   *its = lme->its;
    113:   PetscFunctionReturn(PETSC_SUCCESS);
    114: }

    116: /*@
    117:    LMEGetConvergedReason - Gets the reason why the `LMESolve()` iteration was
    118:    stopped.

    120:    Not Collective

    122:    Input Parameter:
    123: .  lme - the linear matrix equation solver context

    125:    Output Parameter:
    126: .  reason - negative value indicates diverged, positive value converged, see
    127:    `LMEConvergedReason` for the possible values

    129:    Options Database Key:
    130: .  -lme_converged_reason - print reason for convergence/divergence, and number of iterations

    132:    Note:
    133:    If this routine is called before or doing the `LMESolve()` the value of
    134:    `LME_CONVERGED_ITERATING` is returned.

    136:    Level: intermediate

    138: .seealso: [](ch:lme), `LMESetTolerances()`, `LMESolve()`, `LMEConvergedReason`, `LMESetErrorIfNotConverged()`
    139: @*/
    140: PetscErrorCode LMEGetConvergedReason(LME lme,LMEConvergedReason *reason)
    141: {
    142:   PetscFunctionBegin;
    144:   PetscAssertPointer(reason,2);
    145:   *reason = lme->reason;
    146:   PetscFunctionReturn(PETSC_SUCCESS);
    147: }

    149: /*@
    150:    LMEGetErrorEstimate - Returns the error estimate obtained during the solve.

    152:    Not Collective

    154:    Input Parameter:
    155: .  lme - the linear matrix equation solver context

    157:    Output Parameter:
    158: .  errest - the error estimate

    160:    Note:
    161:    This is the error estimated internally by the solver. The actual
    162:    error bound can be computed with `LMEComputeError()`. Note that some
    163:    solvers may not be able to provide an error estimate.

    165:    Level: advanced

    167: .seealso: [](ch:lme), `LMEComputeError()`
    168: @*/
    169: PetscErrorCode LMEGetErrorEstimate(LME lme,PetscReal *errest)
    170: {
    171:   PetscFunctionBegin;
    173:   PetscAssertPointer(errest,2);
    174:   *errest = lme->errest;
    175:   PetscFunctionReturn(PETSC_SUCCESS);
    176: }

    178: /*
    179:    LMEComputeResidualNorm_Lyapunov - Computes the Frobenius norm of the residual matrix
    180:    associated with the Lyapunov equation.
    181: */
    182: static PetscErrorCode LMEComputeResidualNorm_Lyapunov(LME lme,PetscReal *norm)
    183: {
    184:   PetscInt          j,n,N,k,l,lda,ldb;
    185:   PetscBLASInt      n_,N_,k_,l_,lda_,ldb_;
    186:   PetscScalar       *Rarray,alpha=1.0,beta=0.0;
    187:   const PetscScalar *A,*B;
    188:   BV                W,AX,X1,C1;
    189:   Mat               R,X1m,C1m;
    190:   Vec               v,w;
    191:   VecScatter        vscat;

    193:   PetscFunctionBegin;
    194:   PetscCall(MatLRCGetMats(lme->C,NULL,&C1m,NULL,NULL));
    195:   PetscCall(MatLRCGetMats(lme->X,NULL,&X1m,NULL,NULL));
    196:   PetscCall(BVCreateFromMat(C1m,&C1));
    197:   PetscCall(BVSetFromOptions(C1));
    198:   PetscCall(BVCreateFromMat(X1m,&X1));
    199:   PetscCall(BVSetFromOptions(X1));
    200:   PetscCall(BVGetSizes(X1,&n,&N,&k));
    201:   PetscCall(BVGetSizes(C1,NULL,NULL,&l));
    202:   PetscCall(PetscBLASIntCast(n,&n_));
    203:   PetscCall(PetscBLASIntCast(N,&N_));
    204:   PetscCall(PetscBLASIntCast(k,&k_));
    205:   PetscCall(PetscBLASIntCast(l,&l_));

    207:   /* create W to store a redundant copy of a BV in each process */
    208:   PetscCall(BVCreate(PETSC_COMM_SELF,&W));
    209:   PetscCall(BVSetSizes(W,N,N,k));
    210:   PetscCall(BVSetFromOptions(W));
    211:   PetscCall(BVGetColumn(X1,0,&v));
    212:   PetscCall(VecScatterCreateToAll(v,&vscat,NULL));
    213:   PetscCall(BVRestoreColumn(X1,0,&v));

    215:   /* create AX to hold the product A*X1 */
    216:   PetscCall(BVDuplicate(X1,&AX));
    217:   PetscCall(BVMatMult(X1,lme->A,AX));

    219:   /* create dense matrix to hold the residual R=C1*C1'+AX*X1'+X1*AX' */
    220:   PetscCall(MatCreateDense(PetscObjectComm((PetscObject)lme),n,n,N,N,NULL,&R));

    222:   /* R=C1*C1' */
    223:   PetscCall(MatDenseGetArrayWrite(R,&Rarray));
    224:   for (j=0;j<l;j++) {
    225:     PetscCall(BVGetColumn(C1,j,&v));
    226:     PetscCall(BVGetColumn(W,j,&w));
    227:     PetscCall(VecScatterBegin(vscat,v,w,INSERT_VALUES,SCATTER_FORWARD));
    228:     PetscCall(VecScatterEnd(vscat,v,w,INSERT_VALUES,SCATTER_FORWARD));
    229:     PetscCall(BVRestoreColumn(C1,j,&v));
    230:     PetscCall(BVRestoreColumn(W,j,&w));
    231:   }
    232:   if (n) {
    233:     PetscCall(BVGetArrayRead(C1,&A));
    234:     PetscCall(BVGetLeadingDimension(C1,&lda));
    235:     PetscCall(PetscBLASIntCast(lda,&lda_));
    236:     PetscCall(BVGetArrayRead(W,&B));
    237:     PetscCall(BVGetLeadingDimension(W,&ldb));
    238:     PetscCall(PetscBLASIntCast(ldb,&ldb_));
    239:     PetscCallBLAS("BLASgemm",BLASgemm_("N","C",&n_,&N_,&l_,&alpha,(PetscScalar*)A,&lda_,(PetscScalar*)B,&ldb_,&beta,Rarray,&n_));
    240:     PetscCall(BVRestoreArrayRead(C1,&A));
    241:     PetscCall(BVRestoreArrayRead(W,&B));
    242:   }
    243:   beta = 1.0;

    245:   /* R+=AX*X1' */
    246:   for (j=0;j<k;j++) {
    247:     PetscCall(BVGetColumn(X1,j,&v));
    248:     PetscCall(BVGetColumn(W,j,&w));
    249:     PetscCall(VecScatterBegin(vscat,v,w,INSERT_VALUES,SCATTER_FORWARD));
    250:     PetscCall(VecScatterEnd(vscat,v,w,INSERT_VALUES,SCATTER_FORWARD));
    251:     PetscCall(BVRestoreColumn(X1,j,&v));
    252:     PetscCall(BVRestoreColumn(W,j,&w));
    253:   }
    254:   if (n) {
    255:     PetscCall(BVGetArrayRead(AX,&A));
    256:     PetscCall(BVGetLeadingDimension(AX,&lda));
    257:     PetscCall(PetscBLASIntCast(lda,&lda_));
    258:     PetscCall(BVGetArrayRead(W,&B));
    259:     PetscCallBLAS("BLASgemm",BLASgemm_("N","C",&n_,&N_,&k_,&alpha,(PetscScalar*)A,&lda_,(PetscScalar*)B,&ldb_,&beta,Rarray,&n_));
    260:     PetscCall(BVRestoreArrayRead(AX,&A));
    261:     PetscCall(BVRestoreArrayRead(W,&B));
    262:   }

    264:   /* R+=X1*AX' */
    265:   for (j=0;j<k;j++) {
    266:     PetscCall(BVGetColumn(AX,j,&v));
    267:     PetscCall(BVGetColumn(W,j,&w));
    268:     PetscCall(VecScatterBegin(vscat,v,w,INSERT_VALUES,SCATTER_FORWARD));
    269:     PetscCall(VecScatterEnd(vscat,v,w,INSERT_VALUES,SCATTER_FORWARD));
    270:     PetscCall(BVRestoreColumn(AX,j,&v));
    271:     PetscCall(BVRestoreColumn(W,j,&w));
    272:   }
    273:   if (n) {
    274:     PetscCall(BVGetArrayRead(X1,&A));
    275:     PetscCall(BVGetLeadingDimension(AX,&lda));
    276:     PetscCall(PetscBLASIntCast(lda,&lda_));
    277:     PetscCall(BVGetArrayRead(W,&B));
    278:     PetscCallBLAS("BLASgemm",BLASgemm_("N","C",&n_,&N_,&k_,&alpha,(PetscScalar*)A,&lda_,(PetscScalar*)B,&ldb_,&beta,Rarray,&n_));
    279:     PetscCall(BVRestoreArrayRead(X1,&A));
    280:     PetscCall(BVRestoreArrayRead(W,&B));
    281:   }
    282:   PetscCall(MatDenseRestoreArrayWrite(R,&Rarray));

    284:   /* compute ||R||_F */
    285:   PetscCall(MatNorm(R,NORM_FROBENIUS,norm));

    287:   PetscCall(BVDestroy(&W));
    288:   PetscCall(VecScatterDestroy(&vscat));
    289:   PetscCall(BVDestroy(&AX));
    290:   PetscCall(MatDestroy(&R));
    291:   PetscCall(BVDestroy(&C1));
    292:   PetscCall(BVDestroy(&X1));
    293:   PetscFunctionReturn(PETSC_SUCCESS);
    294: }

    296: /*@
    297:    LMEComputeError - Computes the error (based on the residual norm) associated
    298:    with the last equation solved.

    300:    Collective

    302:    Input Parameter:
    303: .  lme  - the linear matrix equation solver context

    305:    Output Parameter:
    306: .  error - the error

    308:    Notes:
    309:    This function is not scalable (in terms of memory or parallel communication),
    310:    so it should not be called except in the case of small problem size. For
    311:    large equations, use `LMEGetErrorEstimate()`.

    313:    Level: advanced

    315: .seealso: [](ch:lme), `LMESolve()`, `LMEGetErrorEstimate()`
    316: @*/
    317: PetscErrorCode LMEComputeError(LME lme,PetscReal *error)
    318: {
    319:   PetscFunctionBegin;
    321:   PetscAssertPointer(error,2);

    323:   PetscCall(PetscLogEventBegin(LME_ComputeError,lme,0,0,0));
    324:   /* compute residual norm */
    325:   switch (lme->problem_type) {
    326:     case LME_LYAPUNOV:
    327:       PetscCall(LMEComputeResidualNorm_Lyapunov(lme,error));
    328:       break;
    329:     default:
    330:       SETERRQ(PetscObjectComm((PetscObject)lme),PETSC_ERR_SUP,"Not implemented for equation type %s",LMEProblemTypes[lme->problem_type]);
    331:   }

    333:   /* compute error */
    334:   /* currently we only support absolute error, so just return the norm */
    335:   PetscCall(PetscLogEventEnd(LME_ComputeError,lme,0,0,0));
    336:   PetscFunctionReturn(PETSC_SUCCESS);
    337: }