sparse-matrixchapel

Chapel loop variable undeclared


When I try to compile the following program, the compiler complains that j and row are undeclared, which surprised me because a similar construction in Chapel - Ranges defined using bounds of type 'range(int(64),bounded,false)' are not currently supported was legal. When I declare row, j it prints 0 for both while the loop starts at 1. Declarations auto-initialise to zero, so is the row from forall row in 1..mat.m somehow different from the declaration above?

use BlockDist;

record CSRMatrix {
  /* The matrix is m x n */
  var m: int;
  var n: int;
  /* Number of non-zeroes */
  var nz: int;
  /* Stores the non-zeroes */
  var val: [1..nz] real;
  /* The first non-zero of row k is the row_ptr(k)th non-zero overall.
   * row_ptr(m + 1) is the total number of non-zeroes. */
  var row_ptr: [1..m + 1] int;
  /* The kth non-zero has column-index col_ind(k) */
  var col_ind: [1..nz] int;
}

/* We assume some global sparse matrix A has been distributed blockwise along the first
   dimension and that mat is the local part of the global matrix A(s). Is not 
   distributed from Chapel's POV! 
   vec is distributed blockwise, is distributed from Chapel's POV! 
   Returns matmul(A(s), vec), the local part of matmul(A, vec). */
proc spmv(mat: CSRMatrix, vec: [1..mat.n] real)
{
  var result: [1..mat.m] real = 0.0;

//  var row: int;
//  var j: int;

  forall row in 1..mat.m do
    for j in mat.row_ptr(row)..mat.row_ptr(row + 1) - 1 do
      writeln(row);
      writeln(j);
      result(row) += mat.val(j) * vec(mat.col_ind(j));

  return result;
}

Solution

  • @StrugglingProgrammer's response in the notes is correctly diagnosing the problem here, but to capture the explanation with more rationale and commentary:

    In Chapel, the index variables in for-, foreach-, forall-, and coforall-loops are new variables defined for the scope of the loop's body, as you expected. Thus for i in myIterand declares a new variable i that takes on the values yielded by myIterand—e.g., an integer in the common case of iterating over a range like for i in 1..n and your loops.

    The problem here is that Chapel is not a whitespace-sensitive language (as Python is, say), and its loops have two syntactic forms:

    Thus, when your code says:

    forall row in 1..mat.m do
      for j in mat.row_ptr(row)..mat.row_ptr(row + 1) - 1 do
        writeln(row);
        writeln(j);
        result(row) += mat.val(j) * vec(mat.col_ind(j));
    

    to get the effect desired, you would need to write this as:

    forall row in 1..mat.m do
      for j in mat.row_ptr(row)..mat.row_ptr(row + 1) - 1 {
        writeln(row);
        writeln(j);
        result(row) += mat.val(j) * vec(mat.col_ind(j));
      }
    

    Note that the first forall loop does not require curly brackets because its body is a single statement—the for-loop, which happens to have its own multi-statement body.

    Because of the possibility of this class of errors, particularly as code is evolving, defensive Chapel programmers tend to prefer always using curly brackets to avoid the possibility of this mistake:

    forall row in 1..mat.m {
      for j in mat.row_ptr(row)..mat.row_ptr(row + 1) - 1 {
        writeln(row);
        writeln(j);
        result(row) += mat.val(j) * vec(mat.col_ind(j));
      }
    }
    

    And to be pedantic, your original code, indented to reflect the actual control flow would have been as follows (and a good Chapel-aware editor mode would ideally help you by indenting it in this way):

    forall row in 1..mat.m do
      for j in mat.row_ptr(row)..mat.row_ptr(row + 1) - 1 do
        writeln(row);
    writeln(j);
    result(row) += mat.val(j) * vec(mat.col_ind(j));
    

    Seeing the code written this way, you can see why the compiler complains about j and row in the final two statements, but not about row on the first one, since it is within the nested loop as expected.

    It's also why, when you introduce explicit variables for j and row they evaluate to zero in those lines. Essentially, your loops are introducing new variables that temporarily shadow the explicitly-defined ones, and the two statements that were outside the loop nest refer to those original variables rather than the loops' index variables as expected. As you note, since Chapel variables are default initialized, they have the value 0.

    As one final piece of trivia, since compound statements { ... } are themselves single statements in Chapel, it is legal to write loops with both do and { ... }:

    forall row in 1..mat.m do {
      for j in mat.row_ptr(row)..mat.row_ptr(row + 1) - 1 do {
        writeln(row);
        writeln(j);
        result(row) += mat.val(j) * vec(mat.col_ind(j));
      }
    }
    

    However, this is not at all necessary, so I personally tend to discourage its use, considering it overkill and likely to lead to further misunderstandings.