gire
gire

Reputation: 1105

Slicing a matrix: unresolved access of

I'm digging into Chapel and I am stuck at slicing a matrix inside a function. The function receiving the matrix is the following:

proc outer_function(x: [?DX]) {

    var number_rows: int = x.shape(1);
    var number_columns: int = DX.rank;
    var result: [1..number_columns] real;

    if number_columns == 1 then {
        result[1] = inner_function(x);
    } else {
        var column_domain: domain(1) = {1..number_columns};
        for i in column_domain do {
            result[i] = inner_function(x[1..number_rows, i]);
        }

    }

    return result;
}

I.e., the outer_function can receive a vector or a matrix. If it receives a vector it calls inner_function with the same input parameter. If outer_function receives a matrix, then I want to slice the input matrix by columns and call inner_function.

The issue is that slicing the input matrix with x[1..number_rows, i] or with x[{1..number_rows}, i] will throw an error at compile time:

I need help to find out why I am getting this error, and if there's a more Chapel-like way to achieve what I am trying to do.

Upvotes: 2

Views: 164

Answers (1)

Brad
Brad

Reputation: 4008

The error you're getting is due to the fact that your function's else branch is trying to slice x as though it were a 2D array (since it uses two indexing expressions: 1..number_rows and i) even though some callsites are passing in a 1D array / vector (or so I'm assuming based on your description and error message). By default, Chapel only supports slicing 1D arrays using a single indexing expression and 2D arrays using two indexing expressions; doing otherwise results in unresolved access errors like the ones you're getting.

I believe the fix is fairly simple: You want the compiler to fold your function's conditional such that it only considers the then branch when x is 1D and the else branch when x is 2D. Then, the 2D slicing expression will only be evaluated when it is legal.

Chapel folds conditionals whose tests are param expressions (meaning that the expression can be evaluated at compile-time). The rank of an array is a param value, so your conditional is very close to being folded except that you stored the rank into a variable (var number_columns) and used that in the conditional. Because the compiler can't generally know the values of variables, the presence of the var in the test expression (number_columns == 1) disables its ability to fold the conditional (even though you and I can see that the variable is initialized with a param and not changed thereafter).

One fix would be to declare number_columns to be a param:

param number_columns: int = DX.rank;
...
if number_columns == 1 then {

This will cause the conditional's test to be a param expression since (a) number_columns is now a param, (b) 1 is a param, and (c) Chapel supports an implementation of == that compares param ints and returns a param bool. As a result, the expression can now be evaluated at compile-time, and the conditional will be folded such that only the 1D or 2D version will persist for a given 1D or 2D formal x.

That said, an even simpler fix would be to simply make the test reason about the rank of DX directly:

if DX.rank == 1 then {

This will cause the conditional to be folded for the same reasons as the previous rewrite.

(And note that this may actually be what you want since presumably number_columns ought to be something more like x.shape(2) rather than x's rank, which will cause it to always be 1 or 2?).

To that end, here is a proposed rewrite of your code with illustrative calls and a proposed inner_function() that accepts 1D arrays:

config var n = 3;

var v: [1..n] real = -1;
writeln(v);
writeln(outer_function(v));

var D2 = {1..n, 1..n};
var A: [D2] real = [(i,j) in D2] i + j / 10.0;
writeln(A);
writeln(outer_function(A));

proc outer_function(x: [?DX]) {
  var number_rows: int = x.shape(1);
  var number_columns: int = if x.rank == 1 then 1 else x.shape(2);
  var result: [1..number_columns] real;

  if x.rank == 1 then {
    result[1] = inner_function(x);
  } else {
    var column_domain: domain(1) = {1..number_columns};
    for i in column_domain do {
      result[i] = inner_function(x[1..number_rows, i]);
    }
  }

  return result;
}

proc inner_function(x: [?DX]) {
  if x.rank != 1 then
    compilerError("inner_function only accepts 1D arrays");
  return + reduce x;
}

Note that my inner_function() is similarly relying on folding of conditionals to only generate the compiler error in the event that inner_function() is passed an array whose rank is not 1 (try calling inner_function(A) to trigger this compiler error).

Assuming I'm on the track that you want, here's an even cleaner (and more flexible) implementation of outer_function():

proc outer_function(x: [?DX]) {
  if x.rank == 1 {
    return inner_function(x);
  } else {
    var result: [DX.dim(2)] real;
    for i in result.domain do {
      result[i] = inner_function(x[DX.dim(1), i]);
    }
    return result;
  }
}

Here, I've done two main things and one minor one:

  • taken advantage of the fact that the conditional is going to be folded to have each branch return a different type -- the 1D case will return a scalar and the 2D case will return a 1D array
  • used DX.dim(i) to refer to the ranges defining the dimensions of DX so that this function will work whether DX has 1-based indices or 0-based indices (or b-based indices) and preserve those indices in the result vector that it creates and returns
  • removed the then keyword which is not necessary when using curly brackets / a compound statement to define a conditional's body.

Upvotes: 1

Related Questions