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 Domain Maps (Domain Maps).

Parallel Safety with respect to Arrays (and Domains)

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 (Parallel Safety with respect to Domains (and Arrays)).

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 ] type-expression

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

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. Chapel semantics are defined so that the compiler will never need to insert temporary arrays of the same size as a user array variable.

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 representing values in square brackets. A 0-based domain will automatically be generated for the given array literal. The type of the array’s values will be the type of the first element listed. A trailing comma is allowed.

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.

Note

Future:

Determine the type of a rectangular array literal based on the most promoted type, rather than the first element’s type.

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 differentiate syntactically between array literals for an array of arrays and a multi-dimensional array.

An rectangular array’s default value is for each array element to be 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 domain map. Through this mechanism, users can reason about and control the runtime representation of an array’s elements. See  Domain Maps 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...)
  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);
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 zipper 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 function by ref or const ref depending on whether or not the formal argument is modified. 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 domain map, the actual array’s domain must use an equivalent domain map.

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.

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 Functions and Methods on Arrays

proc isRectangularArr(a: []) param

Warning

isRectangularArr is deprecated - please use isRectangular method on array

proc isIrregularArr(a: []) param

Warning

isIrregularArr is deprecated - please use isIrregular method on array

proc isAssociativeArr(a: []) param

Warning

isAssociativeArr is deprecated - please use isAssociative method on array

proc isSparseArr(a: []) param

Warning

isSparseArr is deprecated - please use isSparse method on array

type array

The array type

proc eltType type

The type of elements contained in the array

proc idxType type

The type of indices used in the array’s domain

proc intIdxType type
proc rank param

The number of dimensions in the array

proc indices

Return the array’s indices as a copy of its domain.

Note

In a forthcoming release, we expect .indices to change in behavior to return/yield indices using a local representation rather than as a clone of the array’s domain. In order to preserve the legacy behavior in your program, please use .domain instead (or a copy thereof).

If you’d like to opt into a prototype of the new behavior, recompile with -sarrayIndicesAlwaysLocal=true. For dense, rectangular arrays, this will have the effect of returning a local domain representing the array’s indices; for a sparse or associative array, it will invoke a serial iterator that yields the array’s indices.

See https://github.com/chapel-lang/chapel/issues/17883 for further details.

Warning

the current behavior of ‘.indices’ on arrays is deprecated; see https://chapel-lang.org/docs/1.25/builtins/ChapelArray.html#ChapelArray.indices for details

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.

iter these() ref

Yield the array elements

proc size

Return the number of elements in the array

proc sizeAs(type t: integral)

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

proc reindex(newDomain: domain)

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 ...)

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

Return the Implicitly Represented Value for sparse arrays

iter sorted(comparator: ?t = chpl_defaultComparator())

Yield the array elements in sorted order.

proc targetLocales const ref

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

proc hasSingleLocalSubdomain() param

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)

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 back()

Warning

Array back() method is deprecated; use last instead

proc first

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

proc front()

Warning

Array front() method is deprecated; use first instead

proc reverse()

Reverse the order of the values in the array.

proc find(val: this.eltType): (bool, index(this.domain))

Return a tuple containing true and the index of the first instance of val in the array, or if val is not found, a tuple containing false and an unspecified value is returned.

proc count(val: this.eltType): int

Return the number of times val occurs in the array.

proc shape

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: []): bool

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 isDmapType(type t) param

Return true if t is a domain map type. Otherwise return false.

proc isDmapValue(e) param

Return true if e is a domain map. Otherwise return false.

proc isArrayType(type t) param

Return true if t is an array type. Otherwise return false.

proc isArrayValue(e) param

Return true if e is an array. 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.