Mat: Matrices in PETSc

This tutorial is an introduction to matrix objects (Mat) in PETSc. Working with a matrix in PETSc involves several steps:

  • Create a matrix

  • Insert or add values into the matrix

  • Process (or assemble) the matrix

  • Use the matrix for computations

  • Destroy the matrix

In this tutorial, we demonstrate how to create a tridiagonal parallel matrix. A matrix of this form can arise, for example, from a three point finite difference discretization of the one-dimensional Laplace equation. We will also show basic matrix and matrix-vector operations. An example of creating a sequential tridiagonal matrix can be found in PETSc KSP tutorial ex1 and a parallel example is shown in PETSc KSP tutorial ex23.

Creating a PETSc matrix

We start by setting up a PETSc program. To utilize the matrix class, we need to include petscmat.h which automatically includes header files for basic PETSc routines, index sets, and viewers. We also need to declare variables to hold the data we will use in this tutorial. In this case, we declare a Mat and two Vec objects, along with other PetscInt and PetscScalar variables that will be used to, for example, set matrix values. PETSc programs usually begin with PetscInitialize, which sets up MPI, among other things.

static char help[] = "Create a tridiagonal matrix.\n\n";

#include <petscmat.h>

int main(int argc,char **args)
{

  Mat            A;
  Vec            x,b;
  PetscInt       i,n=10,col[3],rstart,rend,nlocal,mlocal;
  PetscErrorCode ierr;
  PetscScalar    value[3];

  ierr = PetscInitialize(&argc,&args,(char*)0,help);if (ierr) return ierr;

We create a matrix with the MatCreate function. This will create a sequential matrix if the program is executed with one processor or a parallel matrix if it is executed with more than one processor. PETSc supports a variety of matrix implementations since no single matrix format is appropriate for all problems. For example, sparse matrices can be stored in compressed row (AIJ) format. The matrix type can be coded into a program or set from the command line using, for example, -mat_type mpiaij in the same way that vector formats can be set. We create an n by n square matrix, let PETSc decide how to partition it across processors, and check for command line options.

ierr = MatCreate(PETSC_COMM_WORLD,&A);CHKERRQ(ierr);
ierr = MatSetSizes(A,PETSC_DECIDE,PETSC_DECIDE,n,n);CHKERRQ(ierr);
ierr = MatSetFromOptions(A);CHKERRQ(ierr);
ierr = MatSetUp(A);CHKERRQ(ierr);

To insert or add entries to a matrix, we use the MatSetValues function. A processor can set any entry in a matrix even if the entry is not locally owned, but for efficient assembly it is important that each processor contributes to entries it owns locally (as much as is possible). To accomplish this, we identify the portion of the matrix on each processor and insert values into the matrix. In this example, we insert values one row at a time in a loop by providing the column indices and values. We handle the boundaries separately since these rows only contain two entries. The final step before we can use the matrix is assembly.

ierr = MatGetOwnershipRange(A,&rstart,&rend);CHKERRQ(ierr);

/* Boundary */
if (!rstart) {
  rstart = 1;
  i      = 0; col[0] = 0; col[1] = 1; value[0] = 2.0; value[1] = -1.0;
  ierr   = MatSetValues(A,1,&i,2,col,value,INSERT_VALUES);CHKERRQ(ierr);
}
if (rend == n) {
  rend = n-1;
  i    = n-1; col[0] = n-2; col[1] = n-1; value[0] = -1.0; value[1] = 2.0;
  ierr = MatSetValues(A,1,&i,2,col,value,INSERT_VALUES);CHKERRQ(ierr);
}

/* Interior */
value[0] = -1.0; value[1] = 2.0; value[2] = -1.0;
for (i=rstart; i<rend; i++) {
  col[0] = i-1; col[1] = i; col[2] = i+1;
  ierr   = MatSetValues(A,1,&i,3,col,value,INSERT_VALUES);CHKERRQ(ierr);
}

ierr = MatAssemblyBegin(A,MAT_FINAL_ASSEMBLY);CHKERRQ(ierr);
ierr = MatAssemblyEnd(A,MAT_FINAL_ASSEMBLY);CHKERRQ(ierr);

Creating a compatible vector

Matrices are often used with vectors in computations. When we create a parallel vector to be used with a matrix, we can force it to have the same layout by specifying the local and global size of the vector. Note that we can create one vector from scratch and duplicate it as needed.

ierr = MatGetLocalSize(A,&nlocal,&mlocal);CHKERRQ(ierr);

ierr = VecCreate(PETSC_COMM_WORLD,&x);CHKERRQ(ierr);
ierr = VecSetSizes(x,mlocal,n);CHKERRQ(ierr);
ierr = VecSetFromOptions(x);CHKERRQ(ierr);
ierr = VecDuplicate(x,&b);CHKERRQ(ierr);
ierr = VecSet(x,1.0);CHKERRQ(ierr);

Matrix operations in PETSc

PETSc supports many matrix routines such as those that compute the trace of a matrix and matrix norms. There are also routines that operate on matrices like scaling a matrix or computing its conjugate. Many matrix-vector operations are supported, as well. A full list of matrix routines can be found here and a few are shown below.

ierr = MatMult(A,x,b);CHKERRQ(ierr);
ierr = VecView(b,PETSC_VIEWER_STDOUT_WORLD);CHKERRQ(ierr);

ierr = MatScale(A,2.0);CHKERRQ(ierr);
ierr = MatView(A,PETSC_VIEWER_STDOUT_WORLD);CHKERRQ(ierr);

All PETSc objects should be destroyed when they are no longer needed, so we conclude by freeing work space and finalizing the program.

  ierr = MatDestroy(&A);CHKERRQ(ierr);
  ierr = VecDestroy(&x);CHKERRQ(ierr);
  ierr = VecDestroy(&b);CHKERRQ(ierr);

  ierr = PetscFinalize();
  return ierr;
}

Running this program with two MPI ranks produces the output below.

Vec Object: 2 MPI processes
  type: mpi
Process [0]
1.
0.
0.
0.
0.
Process [1]
0.
0.
0.
0.
1.
Mat Object: 2 MPI processes
  type: mpiaij
row 0: (0, 4.)  (1, -2.)
row 1: (0, -2.)  (1, 4.)  (2, -2.)
row 2: (1, -2.)  (2, 4.)  (3, -2.)
row 3: (2, -2.)  (3, 4.)  (4, -2.)
row 4: (3, -2.)  (4, 4.)  (5, -2.)
row 5: (4, -2.)  (5, 4.)  (6, -2.)
row 6: (5, -2.)  (6, 4.)  (7, -2.)
row 7: (6, -2.)  (7, 4.)  (8, -2.)
row 8: (7, -2.)  (8, 4.)  (9, -2.)
row 9: (8, -2.)  (9, 4.)

Further reading

More information about the Mat class can be found in the documentation.