Tutorial C++ API

Intro

This tutorial illustrates how to build a linear system and solve it with various linear solver algorithm.

We consider the Laplacian problem on a 2D square mesh of size \(N_X \times N_Y\). Unknowns are related to the mesh nodes indexed by \((i,j)\). We use a 5-Points stencil to discretize the problem.

First of all Alien provides tools to initialize the library, the MPI parallel environment to parametrize IO. The following code illustrates how to initialize :

  • the ParallelMng object

  • the TraceMng object

// INITIALIZE PARALLEL ENVIRONMENT
Environment::initialize(argc, argv);

auto parallel_mng = Environment::parallelMng();
auto trace_mng = Environment::traceMng();

auto comm_size = Environment::parallelMng()->commSize();
auto comm_rank = Environment::parallelMng()->commRank();

trace_mng->info() << "NB PROC = " << comm_size;
trace_mng->info() << "RANK    = " << comm_rank;

Arccore::StringBuilder filename("tutorial.log");
Arccore::ReferenceCounter<Arccore::ITraceStream> ofile;
if (comm_size > 1) {
  filename += comm_rank;
  ofile = Arccore::ITraceStream::createFileStream(filename.toString());
  trace_mng->setRedirectStream(ofile.get());
}
trace_mng->finishInitialize();

Alien::setTraceMng(trace_mng);
Alien::setVerbosityLevel(Alien::Verbosity::Debug);

Space

The Space concept enable to modelize the mathematical algebraic real space \(R^N\) of dimension \(N\)

To build this concept several tools are provided:

  • the IndexeManager package provides helper tools to manage Integer IndexSets

  • the Distribution package provides helper tools to manage the partition of IndexSets between MPI processes

int Nx = 10;
int Ny = 10;

/*
 * MESH PARTITION ALONG Y AXIS
 *
 */
int local_ny = Ny / comm_size;
int r = Ny % comm_size;

std::vector<int> y_offset(comm_size + 1);
y_offset[0] = 0;
for (int ip = 0; ip < r; ++ip)
  y_offset[ip + 1] = y_offset[ip] + local_ny + 1;

for (int ip = r; ip < comm_size; ++ip)
  y_offset[ip + 1] = y_offset[ip] + local_ny;

// Define a lambda function to compute node unique ids from the 2D (i,j) coordinates
// (i,j) -> uid = node_uid(i,j)
auto node_uid = [&](int i, int j) { return j * Nx + i; };

/*
 * DEFINITION of Unknowns Unique Ids and  Local Ids
 */
Alien::UniqueArray<UID> uid;
Alien::UniqueArray<LID> lid;
int first_j = y_offset[comm_rank];
int last_j = y_offset[comm_rank + 1];

int index = 0;
for (int j = first_j; j < last_j; ++j) {
  for (int i = 0; i < Nx; ++i) {
    uid.add(node_uid(i, j));
    lid.add(index);
    ++index;
  }
}

/*
 * DEFINITION of an abstract family of unknowns
 */
Alien::DefaultAbstractFamily family(uid, parallel_mng);
Alien::IndexManager index_manager(parallel_mng);

/*
 * Creation of a set of indexes
 */
auto indexSetU = index_manager.buildScalarIndexSet("U", lid, family, 0);

// Combine all index set and create Linear system index system
index_manager.prepare();

auto global_size = index_manager.globalSize();
auto local_size = index_manager.localSize();

trace_mng->info() << "GLOBAL SIZE : " << global_size;
trace_mng->info() << "LOCAL SIZE  : " << local_size;

/*
 * DEFINITION of
 * - Alien Space,
 * - matrix and vector distributions
 * to manage the distribution of indexes between all MPI processes
 */

auto space = Alien::Space(global_size, "MySpace");

auto mdist =
Alien::MatrixDistribution(global_size, global_size, local_size, parallel_mng);
auto vdist = Alien::VectorDistribution(global_size, local_size, parallel_mng);

trace_mng->info() << "MATRIX DISTRIBUTION INFO";
trace_mng->info() << "GLOBAL ROW SIZE : " << mdist.globalRowSize();
trace_mng->info() << "LOCAL ROW SIZE  : " << mdist.localRowSize();
trace_mng->info() << "GLOBAL COL SIZE : " << mdist.globalColSize();
trace_mng->info() << "LOCAL COL SIZE  : " << mdist.localColSize();

trace_mng->info() << "VECTOR DISTRIBUTION INFO";
trace_mng->info() << "GLOBAL SIZE : " << vdist.globalSize();
trace_mng->info() << "LOCAL SIZE  : " << vdist.localSize();

Matrix

The Matrix concept represents a set of \(N_i\) linear equations (rows) \((y_i)\) of \(N_j\) unknowns \((x_j)\) (columns). This represents a linear application \(S_X \mapsto S_Y\) with \(x \in S_X\), \(y \in S_Y\) and \(x \mapsto y=A*x\). Usually the dimension of \(S_X\) and \(S_Y\) are equal, \(N_i=N_j\). In that case the matrix is square.

/*
 * MATRIX CONSTRUCTION STEP
 */
auto A = Alien::Matrix(mdist);

/* FILLING STEP */

alien_info([&] { cout() << "DIRECT ONE STEP FILLING PHASE";}) ;

auto tag = Alien::DirectMatrixOptions::eResetValues;
{
  auto builder = Alien::DirectMatrixBuilder(A, tag, Alien::DirectMatrixOptions::SymmetricFlag::eUnSymmetric);

  // RESERVE 5 non zero entries per row
  builder.reserve(5);
  builder.allocate();

  // LOOP FOLLOWING Y AXE
  for (int j = first_j; j < last_j; ++j) {
    // LOOP FOLLOWING X AXE
    for (int i = 0; i < Nx; ++i) {
      auto n_uid = node_uid(i, j);
      auto n_lid = uid2lid[n_uid];
      auto irow = allUIndex[n_lid];

      // DIAGONAL FILLING
      builder(irow, irow) = 4;

      // OFF DIAG FILLING
      // On bottom
      if (j > 0) {
        auto off_uid = node_uid(i, j - 1);
        auto off_lid = uid2lid[off_uid];
        auto jcol = allUIndex[off_lid];
        if (jcol != -1)
          builder(irow, jcol) = -1;
      }
      // On the left size
      if (i > 0) {
        auto off_uid = node_uid(i - 1, j);
        auto off_lid = uid2lid[off_uid];
        auto jcol = allUIndex[off_lid];
        if (jcol != -1)
          builder(irow, jcol) = -1;
      }
      // on the right side
      if (i < Nx - 1) {
        auto off_uid = node_uid(i + 1, j);
        auto off_lid = uid2lid[off_uid];
        auto jcol = allUIndex[off_lid];
        if (jcol != -1)
          builder(irow, jcol) = -1;
      }
      // On the top
      if (j < Ny - 1) {
        auto off_uid = node_uid(i, j + 1);
        auto off_lid = uid2lid[off_uid];
        auto jcol = allUIndex[off_lid];
        if (jcol != -1)
          builder(irow, jcol) = -1;
      }
    }
  }
}

Vector

The Vector concept represents the set of unknowns \(x=(x_i)\) element of a linear space \(S_X\).

/*
 * VECTOR CONSTRUCTION
 */
auto B = Alien::Vector(vdist);

// VECTOR FILLING STEP
{
  Alien::VectorWriter writer(B);

  // LOOP ALONG Y AXE
  for (int j = first_j; j < last_j; ++j) {
    // LOOP ALONG X AXE
    for (int i = 0; i < Nx; ++i) {
      auto n_uid = node_uid(i, j);
      auto n_lid = uid2lid[n_uid];
      auto irow = allUIndex[n_lid];

      writer[irow] = 1. / (1. + i + j);
    }
  }
}

// VECTOR ACCESSOR
{
  Alien::LocalVectorReader reader(B);
  for (int i = 0; i < reader.size(); ++i) {
    trace_mng->info() << "B[" << i << "]=" << reader[i];
  }
}

Linear Systems resolution

A linear system is reprensented by a matrix \(A\), and two vectors \(B\) and \(X\) where \(B\) is the system right hand side and \(X\) the solution.

Solving the linear system consists in finding the solution X such that \(A*X=B\) applying a linear solver algorithm.

/*
 * LINEAR SYSTEM CONSTRUCTION
 */

auto A = Alien::Matrix(mdist);
auto B = Alien::Vector(vdist);
auto X = Alien::Vector(vdist);

auto solver = createSolver(/*  ... */) ;

solver->init() ;

solver->solve(matrixA, vectorB, vectorX);

Alien::SolverStatus status = solver->getStatus();
if (status.succeeded)
{
    alien_info()([&]{ cout()<<"SOLVER HAS  SUCCEEDED";}) ;

    SimpleCSRLinearAlgebra alg;
    Alien::Vector vectorR(m_vdist);
    alg.mult(matrixA, vectorX, vectorR);
    alg.axpy(-1., vectorB, vectorR);
    Real res = alg.norm2(vectorR);
    alien_info([&] cout() << "RES : " << res;}) ;
  }
  else
    alien_info()([&]{ cout()<<"SOLVER FAILED";}) ;
  solver->getSolverStat().print(Universe().traceMng(), status, "Linear Solver : ");
}

solver->end();