Arrays

An array is a map from a domain’s indices to a collection of variables of homogeneous type. Since Chapel domains support a rich variety of index sets, Chapel arrays are also richer than the traditional linear or rectilinear array types in conventional languages. Like domains, arrays may be distributed across multiple locales without explicitly partitioning them using Chapel’s distributions.

Parallel Safety with respect to Arrays

Users must take care when applying operations to arrays and domains concurrently from distinct tasks. For more information see the Parallel Safety section for domains.

Array Types

An array type is specified by the identity of the domain that it is declared over and the element type of the array. Array types are given by the following syntax:

array-type:
  [ domain-expression[OPT] ] type-expression[OPT]

The domain-expression may specify a domain that the array can be declared over. If the domain-expression is a rectangular domain literal, the curly braces around the literal may be omitted.

The domain-expression and type-expression are optional, but can currently only be omitted when the array type is specified as one of: a formal type expression, a procedure return type, or an iterator yield type.

Example (decls.chpl).

In the code

const D: domain(2) = {1..10, 1..10};
var A: [D] real;

A is declared to be an arithmetic array over rectangular domain D with elements of type real. As a result, it represents a 2-dimensional \(10 \times 10\) real floating point variables indexed using the indices \((1, 1), (1, 2), \ldots, (1, 10), (2, 1), \ldots, (10, 10)\).

An array’s element type can be referred to using the member symbol eltType.

Example (eltType.chpl).

In the following example, x is declared to be of type real since that is the element type of array A.

var A: [D] real;
var x: A.eltType;

Array Values

An array’s value is the collection of its elements’ values. Assignments between array variables are performed by value as described in Array Assignment.

When an array is stored in a const variable, the array elements are immutable. Undefined behavior will result if the domain is modified (see Association of Arrays to Domains) since that would necessarily add or remove elements.

Array literal values can be either rectangular or associative, corresponding to the underlying domain which defines its indices.

array-literal:
  rectangular-array-literal
  associative-array-literal

Rectangular Array Literals

Rectangular array literals are specified by enclosing a comma-separated list of expressions in square brackets, where each expression represents an array element’s value. A trailing comma is permitted after the final array element. For a literal with n expressions, an anonymous domain literal {0..<n} is generated to represent the array’s indices. If the value expressions are all of the same type, that type will be used as the array’s element type. If they are not, the array’s element type is computed using the same type unification logic that is used when inferring the return type of a procedure with an implicit return type (see Implicit Return Types).

rectangular-array-literal:
  [ expression-list ]
  [ expression-list , ]

Example (adecl-literal.chpl).

The following example declares a 5 element rectangular array literal containing strings, then subsequently prints each string element to the console.

var A = ["1", "2", "3", "4", "5"];

for i in 0..4 do
  writeln(A[i]);

Note

Future:

Provide syntax which allows users to specify the domain for a rectangular array literal.

Example (decl-with-anon-domain.chpl).

The following example declares a 2-element array A containing 3-element arrays of real numbers. A is initialized using array literals.

var A: [1..2] [1..3] real = [[1.1, 1.2, 1.3], [2.1, 2.2, 2.3]];

Open issue.

We would like to introduce a syntax for multi-dimensional rectangular array literals, where today we can only express 1D arrays whose elements are themselves 1D arrays.

A rectangular array’s default value is an array in which each element is initialized to the default value of the element type.

Associative Array Literals

Associative array values are specified by enclosing a comma separated list of index-to-value bindings within square brackets. It is expected that the indices in the listing match in type and, likewise, the types of values in the listing also match. A trailing comma is allowed.

associative-array-literal:
  [ associative-expr-list ]
  [ associative-expr-list , ]

associative-expr-list:
  index-expr => value-expr
  index-expr => value-expr, associative-expr-list

index-expr:
  expression

value-expr:
  expression

Open issue.

Currently it is not possible to use other associative domains as values within an associative array literal.

Example (adecl-assocLiteral.chpl).

The following example declares a 5-element associative array literal which maps integers to their corresponding string representation. The indices and their corresponding values are then printed.

var A = [1 => "one", 10 => "ten", 3 => "three", 16 => "sixteen"];

for da in zip(A.domain, A) do
  writeln(da);

Runtime Representation of Array Values

The runtime representation of an array in memory is controlled by its domain’s distribution. Through this mechanism, users can reason about and control the runtime representation of an array’s elements. See  Distributions for more details.

Array Indexing

Arrays can be indexed using index values from the domain over which they are declared. Array indexing is expressed using either parentheses or square brackets. This results in a reference to the element that corresponds to the index value.

Example (array-indexing.chpl).

Given:

var A: [1..10] real;

the first two elements of A can be assigned the value 1.2 and 3.4 respectively using the assignment:

A(1) = 1.2;
A[2] = 3.4;

Except for associative arrays, if an array is indexed using an index that is not part of its domain’s index set, the reference is considered out-of-bounds and a runtime error will occur, halting the program.

Rectangular Array Indexing

Since the indices for multidimensional rectangular domains are tuples, for convenience, rectangular arrays can be indexed using the list of integer values that make up the tuple index. This is semantically equivalent to creating a tuple value out of the integer values and using that tuple value to index the array. For symmetry, 1-dimensional rectangular arrays can be accessed using 1-tuple indices even though their index type is an integral value. This is semantically equivalent to de-tupling the integral value from the 1-tuple and using it to index the array.

Example (array-indexing-2.chpl).

Given:

var A: [1..5, 1..5] real;
var ij: 2*int = (1, 1);

the elements of array A can be indexed using any of the following idioms:

A(ij) = 1.1;
A((1, 2)) = 1.2;
A(1, 3) = 1.3;
A[ij] = -1.1;
A[(1, 4)] = 1.4;
A[1, 5] = 1.5;

Example (index-using-var-arg-tuple.chpl).

The code

proc f(A: [], is...) do
  return A(is);

defines a function that takes an array as the first argument and a variable-length argument list. It then indexes into the array using the tuple that captures the actual arguments. This function works even for one-dimensional arrays because one-dimensional arrays can be indexed into by 1-tuples.

Associative Array Indexing

Indices can be added to associative arrays through the array’s domain.

Example (assoc-add-index.chpl).

Given:

var D : domain(string, parSafe=false);
var A : [D] int;

the array A initially contains no elements. We can change that by adding indices to the domain D:

D.add("a");
D.add("b");

The array A can now be indexed with indices “a” and “b”:

A["a"] = 1;
A["b"] = 2;
var x = A["a"];

Iteration over Arrays

All arrays support iteration via standard for, forall and coforall loops. These loops iterate over all of the array elements as described by its domain. A loop of the form:

[for|forall|coforall] a in A do
  ...a...

is semantically equivalent to:

[for|forall|coforall] i in A.domain do
  ...A[i]...

The iterator variable for an array iteration is a reference to the array element type.

Array Assignment

Array assignment is by value. Arrays can be assigned arrays, ranges, domains, iterators, or tuples as long as the two expressions are compatible in terms of number of dimensions and shape.

Example (assign.chpl).

If A is an array variable and B is an expression of array, range, domain, or tuple type, or an iterator, then the assignment

A = B;

is equivalent to

[(a,b) in zip(A,B)] a = b;

If the zippered iteration is illegal, then the assignment is illegal. This means, for example, that a range cannot be assigned to a multidimensional rectangular array because the two expressions don’t match in shape and can’t be zipped together. Notice that the assignment is implemented using parallelism when possible, and serially otherwise.

Arrays can be assigned tuples of values of their element type if the tuple contains the same number of elements as the array. For multidimensional arrays, the tuple must be a nested tuple such that the nesting depth is equal to the rank of the array and the shape of this nested tuple must match the shape of the array. The values are assigned element-wise.

Arrays can also be assigned single values of their element type. In this case, each element in the array is assigned this value.

Example (assign-2.chpl).

If e is an expression of the element type of the array or a type that can be implicitly converted to the element type of the array, then the assignment

A = e;

is equivalent to

forall a in A do
  a = e;

Array Comparison

With arrays, the equality operator (i.e. ==) is promoted, so the result is an array of booleans. To get a single result use the equals method instead.

arr1 == arr2 // compare each element resulting in an array of booleans
arr1 != arr2 // compare each element resulting in an array of booleans
arr1.equals(arr2) // compare entire arrays resulting in a single boolean

Array Slicing

An array can be sliced using a domain that has the same type as the domain over which it was declared. The result of an array slice is an alias to the subset of the array elements from the original array corresponding to the slicing domain’s index set.

Example (slicing.chpl).

Given the definitions

var OuterD: domain(2) = {0..n+1, 0..n+1};
var InnerD: domain(2) = {1..n, 1..n};
var A, B: [OuterD] real;

the assignment given by

A[InnerD] = B[InnerD];

assigns the elements in the interior of B to the elements in the interior of A.

Rectangular Array Slicing

A rectangular array can be sliced by any rectangular domain that is a subdomain of the array’s defining domain. If the subdomain relationship is not met, an out-of-bounds error will occur. The result is a subarray whose indices are those of the slicing domain and whose elements are an alias of the original array’s.

Rectangular arrays also support slicing by ranges directly. If each dimension is indexed by a range, this is equivalent to slicing the array by the rectangular domain defined by those ranges. These range-based slices may also be expressed using partially unbounded or completely unbounded ranges. This is equivalent to slicing the array’s defining domain by the specified ranges to create a subdomain as described in Array Slicing and then using that subdomain to slice the array.

Rectangular Array Slicing with a Rank Change

For multidimensional rectangular arrays, slicing with a rank change is supported by substituting integral values within a dimension’s range for an actual range. The resulting array will have a rank less than the rectangular array’s rank and equal to the number of ranges that are passed in to take the slice.

Example (array-decl.chpl).

Given an array

var A: [1..n, 1..n] int;

the slice A[1..n, 1] is a one-dimensional array whose elements are the first column of A.

Count Operator

The # operator can be applied to dense rectangular arrays with a tuple argument whose size matches the rank of the array (or optionally an integer in the case of a 1D array). The operator is equivalent to applying the # operator to the array’s domain and using the result to slice the array as described in Section Rectangular Array Slicing.

Swap operator <=>

The <=> operator can be used to swap the contents of two arrays with the same shape.

Array Arguments to Functions

By default, arrays are passed to functions by const, see The Default Intent. Using the ref intent allows modification of the array without creating a copy. The in, inout, and out intent can create copies of arrays.

When a formal argument has array type, the element type of the array can be omitted and/or the domain of the array can be queried or omitted. In such cases, the argument is generic and is discussed in Formal Arguments of Generic Array Types.

If a formal array argument specifies a domain as part of its type signature, the domain of the actual argument must represent the same index set. If the formal array’s domain was declared using an explicit distribution, the actual array’s domain must use an equivalent distribution.

Array Promotion of Scalar Functions

Arrays may be passed to a scalar function argument whose type matches the array’s element type. This results in a promotion of the scalar function as defined in Promotion.

Example (whole-array-ops.chpl).

Whole array operations is a special case of array promotion of scalar functions. In the code

A = B + C;

if A, B, and C are arrays, this code assigns each element in A the element-wise sum of the elements in B and C.

Returning Arrays from Functions

Arrays return by value by default. The ref and const ref return intents can be used to return a reference to an array.

Similarly to array arguments, the element type and/or domain of an array return type can be omitted.

Sparse Arrays

Sparse arrays in Chapel are those whose domain is sparse. A sparse array differs from other array types in that it stores a single value corresponding to multiple indices. This value is commonly referred to as the zero value, but we refer to it as the implicitly replicated value or IRV since it can take on any value of the array’s element type in practice including non-zero numeric values, a class reference, a record or tuple value, etc.

Warning

Sparse domains and arrays are currently unstable. Their functionality is likely to change in the future.

An array declared over a sparse domain can be indexed using any of the indices in the sparse domain’s parent domain. If it is read using an index that is not part of the sparse domain’s index set, the IRV value is returned. Otherwise, the array element corresponding to the index is returned.

Sparse arrays can only be written at locations corresponding to indices in their domain’s index set. In general, writing to other locations corresponding to the IRV value will result in a runtime error.

By default a sparse array’s IRV is defined as the default value for the array’s element type. The IRV can be set to any value of the array’s element type by assigning to a pseudo-field named IRV in the array.

Example (sparse-error.chpl).

The following code example declares a sparse array, SpsA using the sparse domain SpsD (For this example, assume that n\(>\)1). Line 2 assigns two indices to SpsD’s index set and then lines 3–4 store the values 1.1 and 9.9 to the corresponding values of SpsA. The IRV of SpsA will initially be 0.0 since its element type is real. However, the fifth line sets the IRV to be the value 5.5, causing SpsA to represent the value 1.1 in its low corner, 9.9 in its high corner, and 5.5 everywhere else. The final statement is an error since it attempts to assign to SpsA at an index not described by its domain, SpsD.

var SpsD: sparse subdomain(D);
var SpsA: [SpsD] real;
SpsD = ((1,1), (n,n));
SpsA(1,1) = 1.1;
SpsA(n,n) = 9.9;
SpsA.IRV = 5.5;
SpsA(1,n) = 0.0;  // ERROR!

Association of Arrays to Domains

When an array is declared, it is linked during execution to the domain identity over which it was declared. This linkage is invariant for the array’s lifetime and cannot be changed.

When indices are added or removed from a domain, the change impacts the arrays declared over this particular domain. In the case of adding an index, an element is added to the array and initialized to the IRV for sparse arrays, and to the default value for the element type for dense arrays. In the case of removing an index, the element in the array is removed.

When a domain is reassigned a new value, its arrays are also impacted. Values that correspond to indices in the intersection of the old and new domain are preserved in the arrays. Values that could only be indexed by the old domain are lost. Values that can only be indexed by the new domain have elements added to the new array, initialized to the IRV for sparse arrays, and to the element type’s default value for other array types.

For performance reasons, there is an expectation that a method will be added to domains to allow non-preserving assignment, i.e., all values in the arrays associated with the assigned domain will be lost. Today this can be achieved by assigning the array’s domain an empty index set (causing all array elements to be deallocated) and then re-assigning the new index set to the domain.

An array’s domain can only be modified directly, via the domain’s name or an alias created by passing it to a function via default intent. In particular, the domain may not be modified via the array’s .domain method, nor by using the domain query syntax on a function’s formal array argument (Formal Arguments of Generic Array Types).

Rationale.

When multiple arrays are declared using a single domain, modifying the domain affects all of the arrays. Allowing an array’s domain to be queried and then modified suggests that the change should only affect that array. By requiring the domain to be modified directly, the user is encouraged to think in terms of the domain distinctly from a particular array.

In addition, this choice has the beneficial effect that arrays declared via an anonymous domain have a constant domain. Constant domains are considered a common case and have potential compilation benefits such as eliminating bounds checks. Therefore making this convenient syntax support a common, optimizable case seems prudent.

Set Operations on Associative Arrays

Associative arrays (and domains) support a number of operators for set manipulations. The supported set operators are:

+ , |

Union

&

Intersection

-

Difference

^

Symmetric Difference

Consider the following code where A and B are associative arrays:

var C = A op B;

The result C is a new associative array backed by a new associative domain. The domains of A and B are not modified by op.

There are also op= variants that store the result into the first operand.

Consider the following code where A and B are associative arrays:

A op= B;

A must not share its domain with another array, otherwise the program will halt with an error message.

For the += and |= operators, the value from B will overwrite the existing value in A when indices overlap.

Predefined Routines on Arrays

type array : writeSerializable, readDeserializable

The array type

proc postinit()
proc eltType type

The type of elements contained in the array

proc idxType type

The type used to represent the array’s indices. For a multidimensional array, this is the per-dimension type used.

proc fullIdxType type

The type used to represent the array’s indices. For a 1-dimensional or associative array, this will be the same as idxType above. For a multidimensional array, it will be rank * idxType.

proc rank param

The number of dimensions in the array

proc strides param

The strides value of the underlying domain

proc indices  where !this.isSparse() && !this.isAssociative()

Return a dense rectangular array’s indices as a default domain.

iter indices  where this.isSparse() || this.isAssociative()

Yield an irregular array’s indices.

proc dims()

Return a tuple of ranges describing the bounds of a rectangular domain. For a sparse domain, return the bounds of the parent domain.

proc dim(d: int)

Return a range representing the boundary of this domain in a particular dimension.

proc tryCopy() throws

Warning

tryCopy() is subject to change in the future.

iter these() ref

Yield the array elements

proc size : int

Return the number of elements in the array

proc sizeAs(type t: integral) : t

Return the number of elements in the array as the specified type.

proc reindex(newDomain: domain)  where this.domain.isRectangular() && newDomain.isRectangular()

Return an array view over a new domain. The new domain must be of the same rank and size as the original array’s domain.

For example:

var A: [1..10] int;
const D = {6..15};
ref reA = A.reindex(D);
reA[6] = 1; // updates A[1]
proc reindex(newDims ...)  where this.domain.isRectangular()

Return an array view over a new domain defined implicitly by one or more newDims, which must be ranges. The new domain must be of the same rank and size as the original array’s domain.

For example:

var A: [3..4, 5..6] int;
ref reA = A.reindex(13..14, 15..16);
reA[13,15] = 1; // updates A[3,5]
proc IRV  where !this.isSparse()

Return the Implicitly Represented Value for sparse arrays

proc targetLocales() const ref

Return an array of locales over which this array has been distributed.

proc hasSingleLocalSubdomain() param

Warning

‘hasSingleLocalSubdomain’ on arrays is unstable and may change in the future

Return true if the local subdomain can be represented as a single domain. Otherwise return false.

proc localSubdomain(loc: locale = here)

Return the subdomain that is local to loc.

Arguments:

loc : locale – indicates the locale for which the query should take place (defaults to here)

iter localSubdomains(loc: locale = here)

Warning

‘localSubdomains’ on arrays is unstable and may change in the future

Yield the subdomains that are local to loc.

Arguments:

loc : locale – indicates the locale for which the query should take place (defaults to here)

proc isEmpty() : bool

Return true if the array has no elements

proc last

Return the last element in the array. The array must be a rectangular 1-D array.

proc first

Return the first element in the array. The array must be a rectangular 1-D array.

proc find(val: eltType, ref idx: fullIdxType) : bool

Search an array for val, returning whether or not it is found. If the value is found, the index storing it is returned in idx. If multiple copies of it are found, the lexicographically earliest index will be returned. If it is not found, the resulting value of idx is unspecified.

proc find(val: eltType) : fullIdxType

Search a rectangular array with integral indices for val, returning the index where it is found. If the array contains multiple copies of val, the lexicographically earliest index will be returned. If val is not found, domain.lowBound-1 will be returned instead.

Note that for arrays with idxType=int(?w) (signed int indices), if the low bound in a dimension is min(int(w)), the result will not be well-defined.

proc count(val: this.eltType) : int

Return the number of times val occurs in the array.

proc shape : rank*int  where this.isRectangular() || this.isSparse()

Return a tuple of integers describing the size of each dimension. For a sparse array, returns the shape of the parent domain.

proc isRectangular() param

Return true if the argument a is an array with a rectangular domain. Otherwise return false.

proc isIrregular() param

Return true if a is an array with an irregular domain; e.g. not rectangular. Otherwise return false.

proc isAssociative() param

Return true if a is an array with an associative domain. Otherwise return false.

proc isSparse() param

Return true if a is an array with a sparse domain. Otherwise return false.

proc array.equals(that: array) : bool

Warning

the ‘Array.equals()’ method is unstable

Return true if all this array is the same size and shape as argument that and all elements of this array are equal to the corresponding element in that. Otherwise return false.

proc reshape(A: [], D: domain)

Return a copy of the array A containing the same values but in the shape of the domain D. The number of indices in the domain must equal the number of elements in the array. The elements of A are copied into the new array using the default iteration orders over D and A.