Reputation: 1105
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:
x[1..number_rows, i]
: error: unresolved access of '[domain(1,int(64),false)] int(64)' by '[range(int(64),bounded,false), int(64)]'x[{1..number_rows}, i]
: error: unresolved access of '[domain(1,int(64),false)] int(64)' by '[domain(1,int(64),false), int(64)]'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
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 int
s 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:
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 returnsthen
keyword which is not necessary when using curly brackets / a compound statement to define a conditional's body.Upvotes: 1