From bc218206288008aa6509dfe50a544e8af9d7f24a Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 29 Oct 2020 11:52:48 -0700 Subject: [PATCH 01/33] =?UTF-8?q?Began=20drafting=20=F0=9D=91=9B-d=20array?= =?UTF-8?q?=20proposal.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Approved/multidimensional-arrays.md | 507 ++++++++++++++++++++++++++++ 1 file changed, 507 insertions(+) create mode 100644 Approved/multidimensional-arrays.md diff --git a/Approved/multidimensional-arrays.md b/Approved/multidimensional-arrays.md new file mode 100644 index 0000000..ae37e5a --- /dev/null +++ b/Approved/multidimensional-arrays.md @@ -0,0 +1,507 @@ +--- +title: Multidimensional arrays +description: +author: +date: +--- + +# Proposal + +Currently, for any Q# type `'T`, the array type `'T[]` represents an immutable collection of values of type `'T` indexed by a single integer. It would be really helpful to add a new collection type that is indexed by tuples of integers instead, so as to allow for a more natural representation of concepts like matrices and tensors. + +# Justification + +Arrays of arrays as currently supported in Q# can be used to represent matrices and higher-rank tensors, but come with a number of disadvantages as compared with multidimensional arrays: + +- Nested arrays can in general be jagged; having multidimensional arrays as a type can help enforce that arrays are rectangular at the type level. +- Performance: because nested arrays can be jagged, element lookup cannot in general be done as efficiently as with linear indexing. +- Copy-and-update expressions: Using the `w/` operator to modify elements of nested arrays can be awkward. + ```qsharp + mutable matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; + set matrix w/ 2 <- (matrix[2] w/ 1 <- 20); + // matrix is now [[1, 0, 0], [0, 1, 0], [0, 20, 1]] + ``` + +# Description + +TODO: +Describe the proposal and how it ties in with other aspects of the Q# language in more detail. +Provide general information about the relevant mechanisms and their role in Q#, and how the proposal relates to them. + +## Current Status + +Arrays types in Q# can be constructed from any element type `'T` as `'T[]`, including arrays of arrays such as `Int[][]`. +These *jagged arrays* can be used to represent multidimensional arrays of data (informally, _tensors_), such as matrices or vectors. + +TODO: +Describe all aspects of the current version of Q# that will be impacted by the proposed modification. +Describe in detail the current behavior or limitations that make the proposed change necessary. +Describe how the targeted functionality can be achieved with the current version of Q#. +Refer to the examples given below to illustrate your descriptions. + +### Examples + +Example 1: +Representing a complex-valued matrix with jagged arrays. + +```qsharp +open Microsoft.Quantum.Math; + +let y = [ + [Complex(0.0, 0.0), Complex(0.0, -1.0)], + [Complex(0.0, 1.0), Complex(0.0, 0.0)] +]; +// y: Complex[][] +let upperRight = y[0][1]; +// upperRight: Complex = Complex(0.0, -1.0); +``` + +Example 2: +Representing the generators of a stabilizer group as dense arrays of Pauli operators. + +```qsharp +open Microsoft.Quantum.Arrays; + +let perfect = [ + [PauliI, PauliX, PauliZ, PauliZ, PauliX], + [PauliX, PauliZ, PauliZ, PauliX, PauliI], + [PauliZ, PauliZ, PauliX, PauliI, PauliX], + [PauliZ, PauliX, PauliI, PauliX, PauliZ] +]; +// perfect: Pauli[][] +let syndrome = ForEach(Measure(_, register), perfect); +// syndrome: Result[] +``` + +Example 3: +Representing the generators of a stabilizer group as a sparse array of Pauli operators and their indices. + +```qsharp +let shor = [ + [(PauliX, 0), (PauliX, 1)], + [(PauliX, 1), (PauliX, 2)], + [(PauliX, 3), (PauliX, 4)], + [(PauliX, 4), (PauliX, 5)], + [(PauliX, 6), (PauliX, 7)], + [(PauliX, 7), (PauliX, 8)], + + [(PauliZ, 0), (PauliZ, 1), (PauliZ, 2), (PauliZ, 3), (PauliZ, 4), (PauliZ, 6)], + [(PauliZ, 3), (PauliZ, 4), (PauliZ, 6), (PauliZ, 7), (PauliZ, 8), (PauliZ, 9)], +]; +// shor: (Pauli, Int)[][] +``` + +Example 4: +A prior distribution over phases represented as an array of double-precision real numbers. + +```qsharp +open Microsoft.Quantum.Arrays; +open Microsoft.Quantum.Convert; + +// suppose likelihood is a function of type Int -> Double. +let nPoints = 1000; +let prior = ConstantArray(nPoints, 1.0 / IntAsDouble(nPoints)); +mutable posterior = prior; +for ((idx, pr) in Enumerated(posterior)) { + set posterior w/= idx <- posterior[idx] * likelihood(idx); +} +set posterior = PNormalized(posterior, 1.0); +``` + +Example 5: +A function that returns an elementary matrix (that is, a matrix with a one in the (𝑖, 𝑗)th element and zeros elsewhere). + +```qsharp +open Microsoft.Quantum.Arrays; + +function ElementaryMatrix<'T>( + (nRows : Int, nColumns : Int), + (idxRow : Int, idxCol : Int), + zero : 'T, one : 'T +) : 'T[][] { + mutable matrix = ConstantArray( + nRows, + ConstantArray(nCols, zero) + ); + return matrix w/ row <- (matrix[row] w/ col <- one); +} + +let e01 = ElementaryMatrix((2, 2), (0, 1), 0.0, 1.0); +// e01: Double[][] = [[0.0, 1.0], [0.0, 0.0]] +``` + +## Proposed Modification + +Building on the utility of 1-D array notation, this suggestion proposes modifying Q# to include new multidimensional array types `'T[,]`, `'T[,,]`, and so forth. Like values of type `'T[]`, these new multidimensional would also be immutable, and could be manipulated by using the subscript (`[]`) and copy-and-update (`w/`) operators. + +For any type `'T`, this proposal introduces a new two-dimensional array type `'T[,]`, a three-dimensional array type `'T[,,]`, and so forth. + +New values of type `'T[,]` can be written as literals using `#[]` delimiters instead of `[]`, while literals of type `'T[,,]` can be written using `##[]` delimiters (see example 1 below). +The `#` character can be thought of as denoting a rectangular grid, and as constraining one level of indexing to be rectangular. + +Within multidimensional array literals, it is a _compile-time_ error to declare jagged subarrays, such as in Example 1, below. +It is similarly a compile-time error to use a non-literal array expression for part of a mutlidimensional array literal, as is shown in Example 3, below. + +Elements of a value of type `'T[,]` can be retrieved using the subscript operator `[]` with a value of type `(Int, Int)` as the index, as in `data[(0, 1)]`. +For brevity, the parentheses marking the tuple can be dropped in this case, such that `data[(0, 1)]` and `data[0, 1]` are completely equivalent. +Similarly, elements of a value of type `'T[,,]` can be retrieved by subscripting with indices of type `(Int, Int, Int)`. +Multidimensional indices can also be used with the copy-and-update operator (`w/`) to replace elements of multidimensional arrays, as shown in Example 4 below. + +As with one-dimensional arrays, multidimensional arrays can also be subscripted by ranges. +Each axis of a multidimensional arrays can be sliced by _either_ a value of type `Range` or a value of type `Int`; for example, `(Int, Int)`, `(Range, Int)`, `(Int, Range)`, and `(Range, Range)` are valid subscripts for a value of type `'T[,]`. +As shown in Example 5 below, for each `Int` in an index tuple, the dimensionality (aka rank) of the array is reduced by one. +That is, indexing a `'T[,]` by `(Range, Range)` returns a rank-2 array (`'T[,]`), while indexing by `(Int, Range)` or `(Range, Int)` returns an ordinary rank-1 array (`'T[]`). +Just as with indices like `(Int, Int)` and `(Int, Int, Int)`, subscripts that return slices can also be used in copy-and-replace expressions, as shown in Example 6. + +To support multidimensional arrays, this proposal also suggests extending the `Microsoft.Quantum.Arrays` namespace with additional intrinsic library functions for creating, manipulating, and converting arrays of different dimensionality. +For example, for two- and three-dimensional arrays (higher dimensions should follow in a similar fashion): + +- `function Transposed2<'T>(data : 'T[,]) : 'T[,]` +- `function Transposed3<'T>(axes : (Int, Int, Int), data : 'T[,,]) : 'T[,,]` +- `function Transposed4<'T>(axes : (Int, Int, Int, Int), data : 'T[,,,]) : 'T[,,,]` +- `function Concatenated<'T>(left : 'T[], right : 'T[]) : 'T[]` +- `function Concatenated2<'T>(axis : Int, left : 'T[,], right : 'T[,]) : 'T[,]` +- `function Concatenated3<'T>(axis : Int, left : 'T[,,], right : 'T[,,]) : 'T[,,]` +- `function JaggedAsRectangular2<'T>(jagged : 'T[][]) : 'T[,]` +- `function JaggedAsRectangular3<'T>(jagged : 'T[][][]) : 'T[,,]` +- `function Shape2<'T>(array : 'T[,]) : (Int, Int)` +- `function Shape3<'T>(array : 'T[,,]) : (Int, Int, Int)` +- `function ElementAt2<'T>(index : (Int, Int), array : 'T[,]) : 'T` +- `function ElementAt3<'T>(index : (Int, Int, Int), array : 'T[,,]) : 'T` +- `function Subarray2<'T>(indices : (Int, Int)[], array : 'T[,]) : 'T[]` +- `function Subarray3<'T>(indices : (Int, Int, Int)[], array : 'T[,,]) : 'T[]` +- `function ConstantArray2<'T>(shape : (Int, Int), value : 'T) : 'T[,]` +- `function ConstantArray3<'T>(shape : (Int, Int, Int), value : 'T) : 'T[,,]` +- `function EmptyArray2<'T>() : 'T[,]` +- `function EmptyArray3<'T>() : 'T[,,]` +- `function ReducedAlongAxis2<'TInput, 'TOutput>(reduction : ('TInput[] -> 'TOutput), axis : Int, array : 'TInput[,]) : 'TOutput[]` +- `function ReducedAlongAxis3<'TInput, 'TOutput>(reduction : ('TInput[] -> 'TOutput), axis : Int, array : 'TInput[,,]) : 'TOutput[,]` +- `function TimesD2(left : Double[,], right : Double[,]) : Double[,]` +- `function TimesC2(left : Complex[,], right : Complex[,]) : Complex[,]` +- `function Dot2With2<'T>(plus : (('T, 'T) -> 'T), times : (('T, 'T) -> 'T), (idxLeft : Int, idxRight : Int), left : 'T[,], right : 'T[,]) : 'T[,]` +- `function Dot3With2<'T>(plus : (('T, 'T) -> 'T), times : (('T, 'T) -> 'T), (idxLeft : Int, idxRight : Int), left : 'T[,,], right : 'T[,]) : 'T[,,]` +- `function Dot2With3<'T>(plus : (('T, 'T) -> 'T), times : (('T, 'T) -> 'T), (idxLeft : Int, idxRight : Int), left : 'T[,], right : 'T[,,]) : 'T[,,]` +- `function Dot3With3<'T>(plus : (('T, 'T) -> 'T), times : (('T, 'T) -> 'T), (idxLeft : Int, idxRight : Int), left : 'T[,,], right : 'T[,,]) : 'T[,,,]` +- `function Reshaped1To2<'T>(array : 'T[], newShape : (Int, Int)) : 'T[,]` +- `function Reshaped1To3<'T>(array : 'T[], newShape : (Int, Int, Int)) : 'T[,,]` +- `function Reshaped2To3<'T>(array : 'T[,], newShape : (Int, Int, Int)) : 'T[,,]` +- `function Reshaped3To3<'T>(array : 'T[,,], newShape : (Int, Int, Int)) : 'T[,,]` +- `function Reshaped3To2<'T>(array : 'T[,,], newShape : (Int, Int)) : 'T[,]` +- `function Broadcasted2<'TLeft, 'TRight, 'TOutput>(fn : (('TLeft, 'TRight) -> 'TOutput), left : 'TLeft[,], right : 'TRight[,]) : 'TOutput[,]` +- `function Broadcasted3<'T0, 'T1, 'T2, 'TOutput>(fn : (('T0, 'T1, 'T2) -> 'TOutput), in0 : 'T0[,], in1 : 'T1[,], in2 : 'T2[,]) : 'TOutput[,]` +- `function DiagonalMatrix<'T>(diag : 'T[]) : 'T[,]` +- `function Diagonal<'T>(array : 'T[,]) : 'T[]` + +Many of these array functions can be written in Q# itself, while others would require implementation as intrinsics. +Note that above, we have used suffixes like `2` and `3` to denote ranks of inputs and outputs (or even `2With3` when inputs have different ranks), but these could be eliminated using additional language features, as discussed in "Anticipated Interactions with Future Modifications." + +### Examples + +Example 1: +Declaring and indexing into variables of type `Double[,]` and `Int[,,]` using literals. + +```qsharp +let z = #[ // ← #[] marks a two-dimensional array instead of an array of arrays. + // Inside #[] delimiters, [] delimiters refer not to array expressions, but + // to "rows" of the two-dimensional array literal. + [1.0, 0.0], + [0.0, -1.0] +]; +Message($"{z[0, 1]}"); // ← 0.0 + +// Inside of #[] delimiters, it is a compile-time error for the "rows" to be +// uneven: +let incorrect = #[ + [1.0, 2.0], + [10.0, 20.0, 30.0] // ← error, since this would declare a "ragged" array +]; + +let data = ##[ // ← ##[] marks a three-dimensional array + // Inside ##[] delimiters, [] refer to "planes", then to "rows." + [ + [0, 1], + [2, 3] + ], + + [ + [4, 5], + [6, 7] + ] +]; +Message($"{data[0, 1, 0]}"); // ← 6 +``` + +Example 2: +Mixing 1D and multidimensional arrays. + +```qsharp +// Use ##[] declare the first two levels of indexing as being rectangular, +// resulting in a three-dimensional array. +let data = ##[ + // Inside ##[] delimiters, [] refer to "planes", then to "rows." + [ + // After rows, [] denote arrays as elements again. + [[0], [1, 2]], + [[3, 4, 5], [6, 7, 8, 9]] + ], + + [ + [[10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20]], + [[21, 22, 23, 24, 25, 26, 27], [28, 29, 30, 31, 32, 33, 34, 35]] + ] +]; +// data: Int[][,] (that is, a two-dimensional array of arrays of integers) +// Get the zero-th "plane," first "row", zeroth "column," and third element. +Message($"{data[0, 1, 0][2]}"); // ← 5 +``` + +Example 3: +Using expressions as subarrays of multidimensional arrays results in a compile-time error. + +```qsharp +let a = [2, 3]; +// The following is a compile-time error, since `a` is not a 1D array literal +// of length 2. +let data = #[ + [0, 1], + a +]; +// Using a new library function that can `fail` at runtime works, however. +let data = Concatenated(0, // concatenate along the 0th (row) axis + #[ [0, 1] ], + a +); +// data: Int[,] = #[ +// [0, 1], +// [2, 3] +// ]; +``` + +Example 4: +Using the copy-and-replace operator to manipulate multidimensional arrays. + +```qsharp +function ElementaryMatrix( + (nRows : Int, nCols : Int), (idxRow : Int, idxCol : Int) +) : Double[,] { + return ConstantArray2((nRows, nCols), 0.0) w/ (idxRow, idxCol) <- 1.0; +} +``` + +Example 5: +Slicing multidimensional arrays by ranges. + +```qsharp +let data = #[ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8] +]; + +// Slicing an index by a Range does not reduce the dimensionality +// of the resulting array. +let corners = data[0..2..2, 0..2..2]; +// corners: Int[,] = #[[0, 2], [6, 8]] + +// Subscripting by an Int reduces the dimensionality of the resulting array; +// here, since our index has one Int, the dimensionality reduces from +// Int[,] to Int[]. +let firstRow = data[0, ...]; +// firstRow: Int[] = [0, 1, 2] +// The same pattern holds no matter which index we subscript with an Int. +let firstColumn = data[..., 0]; +// firstColumn = [0, 3, 6] + +let data3 = ##[ + [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8] + ], + + [ + [9, 10, 11], + [12, 13, 14], + [15, 16, 17] + ], + + [ + [18, 19, 20], + [21, 22, 23], + [24, 25, 26] + ] +]; +let corners3 = data3[0..2..2, 0..2..2, 0..2..2]; +// corners3: Int[,,] +let firstPlane = data3[0, ..., ...]; +// firstPlane: Int[,] +let firstRowOfFirstPlane = data3[0, 0, ...]; +// firstRowOfFirstPlane: Int[] = [0, 1, 2] +``` + +Example 6: +Using multidimensional slices in copy-and-update expressions. + +```qsharp +let zeros = ConstantArray2((3, 3), 0); + +let withCorners = zeros w/ (0..2..2, 0..2..2) <- #[[1, 2], [3, 4]]; +// withCorners: Int[,] = #[ [1, 0, 2], [0, 0, 0], [3, 0, 4]] + +let withFirstRow = zeros w/ (0, 0..2) <- [1, 2, 3]; +// withFirstRow: Int[,] = #[ [1, 2, 3], [0, 0, 0], [0, 0, 0] ] + +let withFirstColumn = zeros w/ (0..2, 0) <- [1, 2, 3]; +// withFirstColumn: Int[,] = #[ [1, 0, 0], [2, 0, 0], [3, 0, 0] ] +``` + +# Implementation + +**NB: Code samples in this section are intended as pseudocode only, and may not work directly as written.** + +This proposal can be implemented with minimal new data structures in the Q# runtime, using an approach similar to the NumPy library for Python, the NumSharp library for C#, or the ndarray library for Rust. + + +In particular, each of these libraries represents multidimensional arrays by a data structure similar to the following: + +```qsharp +newtype MultidimensionalArray<'T> = ( + Data: 'T[], + Offset: Int, + Shape: Int[], + Strides: Int[] +); +``` + +Together, `::Offset` and `::Strides` specify how to transform multidimensional indices into a linear index for use with `::Data`: + +```qsharp +array[i, j, k] +// implemented by: +array::Data[array::Offset + i * array::Strides[0] + j * array::Strides[1] + k * array::Data[2]] +``` + +This allows for many common array manipulations to be performed without copying. +For example, a function implementing a matrix transpose need not modify `::Data`, but can reverse `::Strides` and `::Shape`: + +```qsharp +function Transposed2<'T>(array : 'T[,]) : 'T[,] { + return array + w/ Strides <- array::Strides[...-1...] + w/ Shape <- array::Shape[...-1...]; +} +``` + +Similarly, `array[..., 0..2...]` can be implemented by doubling `::Strides[1]`: + +```qsharp +array[..., 0..2...] +// implemented by: +array + w/ Strides <- [array::Strides[0], 2 * array::Strides[1]] + w/ Shape <- [array::Shape[0], array::Shape[1] / 2] +``` + +Reversing an axis can be implemented by using negative strides and modifications to `::Offset`. + +By implementing multidimensional arrays in this way, we can reuse the existing infrastructure for immutable single-dimensional arrays. +Moreover, in many cases, multiple distinct multidimensional arrays can share the same `::Data` item without requiring a copy. +For example, in the variable binding `let view = array[0..2..., 0..2...];`, `view::Data` and `array::Data` can be the same single-dimensional array, as the difference between `view` and `data` can be expressed by only modifying `::Strides` and `::Shape`. +The cases where copies may still be required are when reshaping from more indices to less, when using `w/` to update slices, or if a particular intrinsic function or operation is implemented in simulation by interoperating with native libraries such as BLAS and LAPACK. + +## Timeline + +TODO: +List any dependencies that the proposed implementation relies on. +Estimate the resources required to accomplish each step of the proposed implementation. + +# Further Considerations + +TODO: +Provide any context and background information that is needed to discuss the concepts in detail that are related to or impacted by your proposal. + +## Related Mechanisms + +TODO: +Provide detailed information about the mechanisms and concepts that are relevant for or related to your proposal, +as well as their role, realization and purpose within Q#. + +## Impact on Existing Mechanisms + +TODO: +Describe in detail the impact of your proposal on existing mechanisms and concepts within Q#. + +## Anticipated Interactions with Future Modifications + +TODO: +Describe how the proposed modification ties in with possible future developments of Q#. +Describe what developments it can facilitate and/or what functionalities depend on the proposed modification. + +## Alternatives + +### Syntactic Sugar for Jagged Arrays + +As an alternative, one could consider not adding any new types to Q#, but rather extending `w/` to act on values of type `'T[][]`, `'T[][][]` using tuple indices: + +```qsharp +// Construct the elementary matrix 𝑒₀₁ = |0⟩⟨1| as the jagged array +// [[0, 1, 0], [0, 0, 0], [0, 0, 0]]. +let e01 = ConstantArray(3, ConstantArray(3, 0.0)) w/ (0, 1) <- 1.0; +``` + +### Alternative Syntax for Multidimensional Literals + +As an alternative, one could consider keeping the new types and index expressions suggested by this proposal, but modifying the definition of multidimensional literals. + +For example, alternative delimiters could be used to separate rows, planes, and so forth: + +```qsharp +let data3 = [ + 0, 1; // Separate rows with ; + 2, 3;; // Separate planes with ;; + + 4, 5; + 6, 7 +]; +// data3: Int[,,] +``` + +Other delimiters than `;` could be considered to avoid overloading statement separators. +For example, it may be considered less confusing to reuse the `|` character from `?|` expressions or the `\` character from string escapes: + +```qsharp +let x = [ + 0.0, 1.0 | + 1.0, 0.0 +]; + +let z = [ + 1.0, 0.0 \\ // LaTeX-style notation + 0.0, -1.0 +]; +``` + +These alternatives can possibly be combined with `#[]`: + +```qsharp +let bellTableau = #[ // denote 2D array + true, true, false, false; // separate rows instead of nesting [] inside #[] + false, false, true, true +]; +``` + +## Comparison to Alternatives + +TODO: +Compare your proposal to the possible alternatives and compare the advantages and disadvantages of each approach. +Compare in particular their impact on the future development of Q#. + +# Raised Concerns + +Any concerns about the proposed modification will be listed here and can be addressed in the [Response](#response) section below. + +## Response + + + + From 24edd74b20badfd2ee37ec5c35bcc2ad595b81c5 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 29 Oct 2020 14:36:25 -0700 Subject: [PATCH 02/33] Added some more detail. --- Approved/multidimensional-arrays.md | 127 ++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 16 deletions(-) diff --git a/Approved/multidimensional-arrays.md b/Approved/multidimensional-arrays.md index ae37e5a..09688ee 100644 --- a/Approved/multidimensional-arrays.md +++ b/Approved/multidimensional-arrays.md @@ -33,11 +33,7 @@ Provide general information about the relevant mechanisms and their role in Q#, Arrays types in Q# can be constructed from any element type `'T` as `'T[]`, including arrays of arrays such as `Int[][]`. These *jagged arrays* can be used to represent multidimensional arrays of data (informally, _tensors_), such as matrices or vectors. -TODO: -Describe all aspects of the current version of Q# that will be impacted by the proposed modification. -Describe in detail the current behavior or limitations that make the proposed change necessary. -Describe how the targeted functionality can be achieved with the current version of Q#. -Refer to the examples given below to illustrate your descriptions. +While jagged arrays are extremely useful in many contexts, such as representing sparse arrays (e.g.: the "list-of-lists" representation), using jagged arrays to represent vectors, matrices, and tensors requires extensive checking of array bounds to prevent mistakes in the shapes of jagged arrays. ### Examples @@ -411,14 +407,17 @@ The cases where copies may still be required are when reshaping from more indice ## Timeline -TODO: -List any dependencies that the proposed implementation relies on. -Estimate the resources required to accomplish each step of the proposed implementation. +This proposal does not depend on any other proposals (though it can be made easier-to-use in combination with other proposals; see _Anticipated Interactions with Future Modifications_, below). -# Further Considerations +Required implementation steps: -TODO: -Provide any context and background information that is needed to discuss the concepts in detail that are related to or impacted by your proposal. +- Adapt compiler to recognize new types, subscript expressions, copy-and-replace expressions, and new literal expressions. +- Implement new data structures in QIR and simulation runtime to represent values of new type, and fundamental operations on new type (e.g.: `w/` expressions). +- Design, review, and approve API design for extensions to Microsoft.Quantum.Arrays to support new feature. +- Implement new library functions from previous step. +- Document new multidimensional arrays in language specification and conceptual documentation. + +# Further Considerations ## Related Mechanisms @@ -428,14 +427,110 @@ as well as their role, realization and purpose within Q#. ## Impact on Existing Mechanisms -TODO: -Describe in detail the impact of your proposal on existing mechanisms and concepts within Q#. +This proposal would not modify or invalidate existing functionality (e.g.: `Int[][]` will continue to be a valid type in Q#), and thus is not a breaking change. + +If, in a future proposal, we were to unify multidimensional array with existing functionality using features outlined in _Anticipated Interactions with Future Modifications_, a breaking change would likely be required at that point in time. ## Anticipated Interactions with Future Modifications -TODO: -Describe how the proposed modification ties in with possible future developments of Q#. -Describe what developments it can facilitate and/or what functionalities depend on the proposed modification. +### Handling runtime failure modalities + +Some of the type conversions described above can fail at runtime, decreasing the safety of Q# programs. +To assist, the discriminated union and type-parameterized UDT feature suggestions could be used to represent the possibility of runtime failures in a typesafe fashion. + +For example, the `JaggedAsRectangular2` function above could fail if its input is not actually rectangular. +Using `Maybe<'T>`, we could represent this directly: + +```qsharp +function MaybeJaggedAsRectangular2<'T>(input : 'T[][]) : Maybe<'T[,]> { + if (IsRectangular2(input)) { + // convert here + } else { + return Maybe<'T[,]>::None(); + } +} +``` + +If in a particular application, it is definitely the case that a given jagged array can be converted, the `Maybe` can be unwrapped using a function that attempts to match on values of type `Maybe<'T>`: + +```qsharp +function Forced<'T>(maybe : Maybe<'T>) : 'T { + return maybe match { + Some(value) -> value, + None() -> fail "was None()" + }; +} +``` + +### Removing type suffixes with bounded polymorphism + +Were the bounded polymorphism feature suggested at to be adopted, the different "overloads" for the library functions suggested in this proposal could be consolidated into a smaller number of concepts that then get implemented by each of `'T[,]`, `'T[,,]`, and so forth. + +For example: + +```qsharp +// We define a new concept to represent subscripting a +// value by an index. +concept 'Array is IndexableBy<'Index, 'Element> when { + function ElementAt(index : 'Index, array : 'Array) : 'Element; +} +// We can then use that concept to explain that +// 'T[] is indexed by an Int to return a 'T in the +// same way that 'T[,] is indexed by (Int, Int). +example <'T> 'T[] is IndexableBy { + function ElementAt(index : Int, array : 'T[,]) { + return array[index]; + } +} +example <'T> 'T[,] is IndexableBy<(Int, Int), 'T> { + function ElementAt(index : (Int, Int), array : 'T[,]) { + return array[index]; + } +} +example <'T> 'T[,,] is IndexableBy<(Int, Int, Int), 'T> { + function ElementAt(index : (Int, Int, Int), array : 'T[,]) { + return array[index]; + } +} +// This allows us to use the concept to write out more +// general functions acting on arrays of different +// dimensionality. +function Subarray<'Array, 'Index, 'Element where 'Array is IndexableBy<'Index, 'Element>>( + array : 'Array, + indices : 'Index[] +) : 'Element[] { + mutable subarray = EmptyArray<'Element>(); + for (index in indices) { + // We can use ElementAt to handle + // arrays of different dimensionality in + // a uniform fashion. + set subarray += ElementAt(index, array); + } + return subarray; +} + +// Similarly, if we use type-parameterized UDTs and +// discriminated unions, we can define a concept that +// specifies when a value of a given type can possibly +// be converted to another type. +concept 'TOutput is MaybeConvertableFrom<'TInput> when { + function MaybeAs(input : 'TInput) : Maybe<'TOutput>; +} +// With that concept in place, we can say that some jagged +// arrays can be converted to rectangular arrays, obviating +// the need for JaggedAsRectangular2, JaggedAsRectangular3, +// and so forth. +example <'T> 'T[,] is MaybeConvertableFrom<'T[][]> when { + function MaybeAs(input : 'T[][]) : 'T[,] { + body intrinsic; + } +} +example <'T> 'T[,,] is MaybeConvertableFrom<'T[][][]> when { + function MaybeAs(input : 'T[][][]) : 'T[,,] { + body intrinsic; + } +} +``` ## Alternatives From 56614521ae440aa61c4b4dd709f721f2298cabd0 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 29 Oct 2020 16:57:43 -0700 Subject: [PATCH 03/33] Added missing links and sections. --- Approved/multidimensional-arrays.md | 93 +++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 19 deletions(-) diff --git a/Approved/multidimensional-arrays.md b/Approved/multidimensional-arrays.md index 09688ee..e42adf8 100644 --- a/Approved/multidimensional-arrays.md +++ b/Approved/multidimensional-arrays.md @@ -1,8 +1,8 @@ --- title: Multidimensional arrays -description: -author: -date: +description: Proposal for multidimensional array types in Q# +author: cgranade +date: 29-10-2020 --- # Proposal @@ -24,10 +24,6 @@ Arrays of arrays as currently supported in Q# can be used to represent matrices # Description -TODO: -Describe the proposal and how it ties in with other aspects of the Q# language in more detail. -Provide general information about the relevant mechanisms and their role in Q#, and how the proposal relates to them. - ## Current Status Arrays types in Q# can be constructed from any element type `'T` as `'T[]`, including arrays of arrays such as `Int[][]`. @@ -148,8 +144,11 @@ Each axis of a multidimensional arrays can be sliced by _either_ a value of type As shown in Example 5 below, for each `Int` in an index tuple, the dimensionality (aka rank) of the array is reduced by one. That is, indexing a `'T[,]` by `(Range, Range)` returns a rank-2 array (`'T[,]`), while indexing by `(Int, Range)` or `(Range, Int)` returns an ordinary rank-1 array (`'T[]`). Just as with indices like `(Int, Int)` and `(Int, Int, Int)`, subscripts that return slices can also be used in copy-and-replace expressions, as shown in Example 6. +When using `Range` values to index one or more axes in a multidimensional array, `...` is shorthand the `Range` value `0..1..(n - 1)` where `n` is the length of the axes being indexed. + +When used in `for` loops, multidimensional arrays iterate "on the left," yielding loop variables of one rank lower than the array being looped over, as shown in Example 7, below. -To support multidimensional arrays, this proposal also suggests extending the `Microsoft.Quantum.Arrays` namespace with additional intrinsic library functions for creating, manipulating, and converting arrays of different dimensionality. +Finally, to support multidimensional arrays, this proposal also suggests extending the `Microsoft.Quantum.Arrays` namespace with additional intrinsic library functions for creating, manipulating, and converting arrays of different dimensionality. For example, for two- and three-dimensional arrays (higher dimensions should follow in a similar fashion): - `function Transposed2<'T>(data : 'T[,]) : 'T[,]` @@ -351,12 +350,60 @@ let withFirstColumn = zeros w/ (0..2, 0) <- [1, 2, 3]; // withFirstColumn: Int[,] = #[ [1, 0, 0], [2, 0, 0], [3, 0, 0] ] ``` +Example 7: +Iterating over multidimensional arrays. + +```qsharp +let data3 = ##[ + [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8] + ], + + [ + [9, 10, 11], + [12, 13, 14], + [15, 16, 17] + ], + + [ + [18, 19, 20], + [21, 22, 23], + [24, 25, 26] + ] +]; +// data3: Int[,,] + +for (plane in data) { + // plane: Int[,] + for (row in plane) { + // row: Int[] + Message($"{row}"); + } + + Message(""); +} +// Output +// ====== +// [0, 1, 2] +// [3, 4, 5] +// [6, 7, 8] +// +// [9, 10, 11] +// [12, 13, 14] +// [15, 16, 17] +// +// [18, 19, 20] +// [21, 22, 23] +// [24, 25, 26] +``` + # Implementation **NB: Code samples in this section are intended as pseudocode only, and may not work directly as written.** -This proposal can be implemented with minimal new data structures in the Q# runtime, using an approach similar to the NumPy library for Python, the NumSharp library for C#, or the ndarray library for Rust. - +This proposal can be implemented with minimal new data structures in the Q# runtime, using an approach similar to the [NumPy library for Python](http://numpy.scipy.org/), the [NumSharp library for C#](https://github.com/SciSharp/NumSharp), or the [ndarray library for Rust](https://github.com/rust-ndarray/ndarray/issues/597). In particular, each of these libraries represents multidimensional arrays by a data structure similar to the following: @@ -413,7 +460,7 @@ Required implementation steps: - Adapt compiler to recognize new types, subscript expressions, copy-and-replace expressions, and new literal expressions. - Implement new data structures in QIR and simulation runtime to represent values of new type, and fundamental operations on new type (e.g.: `w/` expressions). -- Design, review, and approve API design for extensions to Microsoft.Quantum.Arrays to support new feature. +- Design, review, and approve API design for extensions to the `Microsoft.Quantum.Arrays namespace` to support the new feature. - Implement new library functions from previous step. - Document new multidimensional arrays in language specification and conceptual documentation. @@ -421,9 +468,14 @@ Required implementation steps: ## Related Mechanisms -TODO: -Provide detailed information about the mechanisms and concepts that are relevant for or related to your proposal, -as well as their role, realization and purpose within Q#. +This proposal generalizes the existing array feature in Q#. +As such, the new features introduced by this proposal are designed to keep to user expectations formed by existing features. +In particular: + +- Like existing arrays, multidimensional arrays are immutable values. +- Mutability can be handled using `set ... w/= ...;` statements in the same fashion as existing 1D arrays. +- Multidimensional arrays can be used as collections in loops. +- There are no subtyping relationships between array types. In particular, `'T[,]` is not a subtype of `'T[][]` but a distinct type in its own right; nor is `('T => Unit is Adj)[,]` a subtype of `('T => Unit)[,]`. ## Impact on Existing Mechanisms @@ -436,7 +488,7 @@ If, in a future proposal, we were to unify multidimensional array with existing ### Handling runtime failure modalities Some of the type conversions described above can fail at runtime, decreasing the safety of Q# programs. -To assist, the discriminated union and type-parameterized UDT feature suggestions could be used to represent the possibility of runtime failures in a typesafe fashion. +To assist, the discriminated union and type-parameterized UDT feature suggestions (https://github.com/microsoft/qsharp-compiler/issues/406) could be used to represent the possibility of runtime failures in a typesafe fashion. For example, the `JaggedAsRectangular2` function above could fail if its input is not actually rectangular. Using `Maybe<'T>`, we could represent this directly: @@ -464,7 +516,7 @@ function Forced<'T>(maybe : Maybe<'T>) : 'T { ### Removing type suffixes with bounded polymorphism -Were the bounded polymorphism feature suggested at to be adopted, the different "overloads" for the library functions suggested in this proposal could be consolidated into a smaller number of concepts that then get implemented by each of `'T[,]`, `'T[,,]`, and so forth. +Were the bounded polymorphism feature suggested at https://github.com/microsoft/qsharp-compiler/issues/557 to be adopted, the different "overloads" for the library functions suggested in this proposal could be consolidated into a smaller number of concepts that then get implemented by each of `'T[,]`, `'T[,,]`, and so forth. For example: @@ -587,9 +639,12 @@ let bellTableau = #[ // denote 2D array ## Comparison to Alternatives -TODO: -Compare your proposal to the possible alternatives and compare the advantages and disadvantages of each approach. -Compare in particular their impact on the future development of Q#. +### Comparison to Syntactic Sugar for Jagged Arrays + +While providing syntactic sugar for copy-and-update operations on jagged arrays helps address some of the more severe pain points in using jagged arrays to represent multidimensional data, that alternative does not address a few critical points: + +- Multidimensional indices cannot efficiently be converted into linear indices, causing performance problems with common matrix and tensor operations. +- Jagged arrays do not guarantee at compile time that data is rectangular, introducing the possibility of runtime logic errors with respect to the shape of multidimensional data. # Raised Concerns From 065fda2208a4fe6a4c981a1f1f486279d06d1030 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 2 Feb 2021 18:52:39 -0800 Subject: [PATCH 04/33] Update and begin splitting out library proposal. --- Approved/multidimensional-arrays.md | 109 +++++++++++++++++----------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/Approved/multidimensional-arrays.md b/Approved/multidimensional-arrays.md index e42adf8..55711e4 100644 --- a/Approved/multidimensional-arrays.md +++ b/Approved/multidimensional-arrays.md @@ -148,47 +148,21 @@ When using `Range` values to index one or more axes in a multidimensional array, When used in `for` loops, multidimensional arrays iterate "on the left," yielding loop variables of one rank lower than the array being looped over, as shown in Example 7, below. -Finally, to support multidimensional arrays, this proposal also suggests extending the `Microsoft.Quantum.Arrays` namespace with additional intrinsic library functions for creating, manipulating, and converting arrays of different dimensionality. -For example, for two- and three-dimensional arrays (higher dimensions should follow in a similar fashion): - -- `function Transposed2<'T>(data : 'T[,]) : 'T[,]` -- `function Transposed3<'T>(axes : (Int, Int, Int), data : 'T[,,]) : 'T[,,]` -- `function Transposed4<'T>(axes : (Int, Int, Int, Int), data : 'T[,,,]) : 'T[,,,]` -- `function Concatenated<'T>(left : 'T[], right : 'T[]) : 'T[]` -- `function Concatenated2<'T>(axis : Int, left : 'T[,], right : 'T[,]) : 'T[,]` -- `function Concatenated3<'T>(axis : Int, left : 'T[,,], right : 'T[,,]) : 'T[,,]` -- `function JaggedAsRectangular2<'T>(jagged : 'T[][]) : 'T[,]` -- `function JaggedAsRectangular3<'T>(jagged : 'T[][][]) : 'T[,,]` -- `function Shape2<'T>(array : 'T[,]) : (Int, Int)` -- `function Shape3<'T>(array : 'T[,,]) : (Int, Int, Int)` -- `function ElementAt2<'T>(index : (Int, Int), array : 'T[,]) : 'T` -- `function ElementAt3<'T>(index : (Int, Int, Int), array : 'T[,,]) : 'T` -- `function Subarray2<'T>(indices : (Int, Int)[], array : 'T[,]) : 'T[]` -- `function Subarray3<'T>(indices : (Int, Int, Int)[], array : 'T[,,]) : 'T[]` -- `function ConstantArray2<'T>(shape : (Int, Int), value : 'T) : 'T[,]` -- `function ConstantArray3<'T>(shape : (Int, Int, Int), value : 'T) : 'T[,,]` -- `function EmptyArray2<'T>() : 'T[,]` -- `function EmptyArray3<'T>() : 'T[,,]` -- `function ReducedAlongAxis2<'TInput, 'TOutput>(reduction : ('TInput[] -> 'TOutput), axis : Int, array : 'TInput[,]) : 'TOutput[]` -- `function ReducedAlongAxis3<'TInput, 'TOutput>(reduction : ('TInput[] -> 'TOutput), axis : Int, array : 'TInput[,,]) : 'TOutput[,]` -- `function TimesD2(left : Double[,], right : Double[,]) : Double[,]` -- `function TimesC2(left : Complex[,], right : Complex[,]) : Complex[,]` -- `function Dot2With2<'T>(plus : (('T, 'T) -> 'T), times : (('T, 'T) -> 'T), (idxLeft : Int, idxRight : Int), left : 'T[,], right : 'T[,]) : 'T[,]` -- `function Dot3With2<'T>(plus : (('T, 'T) -> 'T), times : (('T, 'T) -> 'T), (idxLeft : Int, idxRight : Int), left : 'T[,,], right : 'T[,]) : 'T[,,]` -- `function Dot2With3<'T>(plus : (('T, 'T) -> 'T), times : (('T, 'T) -> 'T), (idxLeft : Int, idxRight : Int), left : 'T[,], right : 'T[,,]) : 'T[,,]` -- `function Dot3With3<'T>(plus : (('T, 'T) -> 'T), times : (('T, 'T) -> 'T), (idxLeft : Int, idxRight : Int), left : 'T[,,], right : 'T[,,]) : 'T[,,,]` -- `function Reshaped1To2<'T>(array : 'T[], newShape : (Int, Int)) : 'T[,]` -- `function Reshaped1To3<'T>(array : 'T[], newShape : (Int, Int, Int)) : 'T[,,]` -- `function Reshaped2To3<'T>(array : 'T[,], newShape : (Int, Int, Int)) : 'T[,,]` -- `function Reshaped3To3<'T>(array : 'T[,,], newShape : (Int, Int, Int)) : 'T[,,]` -- `function Reshaped3To2<'T>(array : 'T[,,], newShape : (Int, Int)) : 'T[,]` -- `function Broadcasted2<'TLeft, 'TRight, 'TOutput>(fn : (('TLeft, 'TRight) -> 'TOutput), left : 'TLeft[,], right : 'TRight[,]) : 'TOutput[,]` -- `function Broadcasted3<'T0, 'T1, 'T2, 'TOutput>(fn : (('T0, 'T1, 'T2) -> 'TOutput), in0 : 'T0[,], in1 : 'T1[,], in2 : 'T2[,]) : 'TOutput[,]` -- `function DiagonalMatrix<'T>(diag : 'T[]) : 'T[,]` -- `function Diagonal<'T>(array : 'T[,]) : 'T[]` - -Many of these array functions can be written in Q# itself, while others would require implementation as intrinsics. -Note that above, we have used suffixes like `2` and `3` to denote ranks of inputs and outputs (or even `2With3` when inputs have different ranks), but these could be eliminated using additional language features, as discussed in "Anticipated Interactions with Future Modifications." +Finally, to support multidimensional arrays, this proposal also suggests extending the `Microsoft.Quantum.Arrays` namespace with the following functions that can be used to implement libraries for working with multidimensional arrays: + +- `internal function AsNDArray2<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,]` +- `internal function AsNDArray3<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,]` +- `internal function AsNDArray4<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,,]` +- `internal function AsNDArray5<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,,,]` +- `internal function AsNDArray6<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,,,,]` +- `internal function AsNDArray7<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,,,,,]` +- `internal function AsNDArray8<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,,,,,,]` +- `internal function NDArrayData<'TElement, 'TArray>(data : 'TArray) : 'TElement[]` +- `internal function NDArrayStrides<'TArray>(data : 'TArray) : Int[]` +- `internal function NDArrayShape<'TArray>(data : 'TArray) : Int[]` +- `internal function NDArrayOffset<'TArray>(data : 'TArray) : Int` + +Each of these five functions would be `body intrinsic;`, and together form the contract between the runtime and the core Q# libraries needed to support this proposed feature (see Example 7, below). By necessity, these functions are "unsafe," in that direct access to these functions would allow violating invariants of multidimensional arrays or bypass the type system to expose runtime failures, necessitating the `internal` modifier. ### Examples @@ -399,6 +373,59 @@ for (plane in data) { // [24, 25, 26] ``` +Example 7: +Implementing array library functions using internal functions. + +```qsharp +namespace Microsoft.Quantum.Arrays { + open Microsoft.Quantum.Diagnostics; + + function Transposed2<'T>(array : 'T[,]) : 'T[,] { + // Start by deconstructing the input using the internal intrinsic + // functions from this proposal; see Implementation below. + let data = NDArrayData<'T, 'T[,]>(array); + let strides = NDArrayStrides(array); + let offset = NDArrayOffset(array); + let shape = NDArrayShape(array); + + // Now use AsNDArray2 to reconstruct, but with shape and strides + // reversed. + return AsNDArray2(data, strides, offset, shape); + } + + function ConstantArray2<'T>(shape : (Int, Int), element : 'T) : 'T[,] { + Fact(Fst(shape) >= 0, "First axis had negative length."); + Fact(Snd(shape) >= 0, "Second axis had negative length."); + + // Here, we set a stride of zero to store everything as a single + // element. Using the copy-and-update operator will require actually + // allocating the whole array, but we can start off by "cheating." + return AsNDArray2([element], [0, 0], 0, shape); + } + + function Shape2<'T>(array : 'T[,]) : (Int, Int) { + let shape = NDArrayShape(array); + return (shape[0], shape[1]); + } + + function TimesD2(left : Double[,], right : Double[,]) : Double[,] { + // For simplicity, we presume that left and right already match each + // other's shape exactly. In an actual library implementation, we would + // want to generalize this to allow arbitrary binary operators, and to + // handle broadcasting between the two inputs. + + mutable data = []; + let (nRows, nCols) = Shape2(left); + for idxCol in 0..nCols - 1 { + for idxRow in 0..nRows - 1 { + set data += [left[(idxRow, idxCol)] * right[(idxRow, idxCol)]]; + } + } + return AsNDArray2(data, [1, nRows], 0, shape); + } +} +``` + # Implementation **NB: Code samples in this section are intended as pseudocode only, and may not work directly as written.** From b5880b97b0b8f1568ca5ef0baaa25940a1aa3e93 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Fri, 26 Feb 2021 19:02:56 -0800 Subject: [PATCH 05/33] Updated proposal based on feedback. --- Approved/3-multidimensional-arrays.md | 698 ++++++++++++++++++++++++++ 1 file changed, 698 insertions(+) create mode 100644 Approved/3-multidimensional-arrays.md diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md new file mode 100644 index 0000000..2f56647 --- /dev/null +++ b/Approved/3-multidimensional-arrays.md @@ -0,0 +1,698 @@ +--- +title: Multidimensional arrays +description: Proposal for multidimensional array types in Q# +author: cgranade +date: 29-10-2020 +--- + +# Proposal + +Currently, for any Q# type `'T`, the array type `'T[]` represents an immutable collection of values of type `'T` indexed by a single integer. It would be really helpful to add a new collection type that is indexed by tuples of integers instead, so as to allow for a more natural representation of concepts like matrices and tensors. + +# Justification + +Arrays of arrays as currently supported in Q# can be used to represent matrices and higher-rank tensors, but come with a number of disadvantages as compared with multidimensional arrays: + +- Nested arrays can in general be jagged; having multidimensional arrays as a type can help enforce that arrays are rectangular at the type level. +- Performance: because nested arrays can be jagged, element lookup cannot in general be done as efficiently as with linear indexing. +- Copy-and-update expressions: Using the `w/` operator to modify elements of nested arrays can be awkward. + ```qsharp + mutable matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; + set matrix w/ 2 <- (matrix[2] w/ 1 <- 20); + // matrix is now [[1, 0, 0], [0, 1, 0], [0, 20, 1]] + ``` + +# Description + +## Current Status + +Arrays types in Q# can be constructed from any element type `'T` as `'T[]`, including arrays of arrays such as `[Int][]`. +These *jagged arrays* can be used to represent multidimensional arrays of data (informally, _tensors_), such as matrices or vectors. + +While jagged arrays are extremely useful in many contexts, such as representing sparse arrays (e.g.: the "list-of-lists" representation), using jagged arrays to represent vectors, matrices, and tensors requires extensive checking of array bounds to prevent mistakes in the shapes of jagged arrays. + +### Examples + +Example 1: +Representing a complex-valued matrix with jagged arrays. + +```qsharp +open Microsoft.Quantum.Math; + +let y = [ + [Complex(0.0, 0.0), Complex(0.0, -1.0)], + [Complex(0.0, 1.0), Complex(0.0, 0.0)] +]; +// y: Complex[][] +let upperRight = y[0][1]; +// upperRight: Complex = Complex(0.0, -1.0); +``` + +Example 2: +Representing the generators of a stabilizer group as dense arrays of Pauli operators. + +```qsharp +open Microsoft.Quantum.Arrays; + +let perfect = [ + [PauliI, PauliX, PauliZ, PauliZ, PauliX], + [PauliX, PauliZ, PauliZ, PauliX, PauliI], + [PauliZ, PauliZ, PauliX, PauliI, PauliX], + [PauliZ, PauliX, PauliI, PauliX, PauliZ] +]; +// perfect: Pauli[][] +let syndrome = ForEach(Measure(_, register), perfect); +// syndrome: Result[] +``` + +Example 3: +Representing the generators of a stabilizer group as a sparse array of Pauli operators and their indices. + +```qsharp +let shor = [ + [(PauliX, 0), (PauliX, 1)], + [(PauliX, 1), (PauliX, 2)], + [(PauliX, 3), (PauliX, 4)], + [(PauliX, 4), (PauliX, 5)], + [(PauliX, 6), (PauliX, 7)], + [(PauliX, 7), (PauliX, 8)], + + [(PauliZ, 0), (PauliZ, 1), (PauliZ, 2), (PauliZ, 3), (PauliZ, 4), (PauliZ, 6)], + [(PauliZ, 3), (PauliZ, 4), (PauliZ, 6), (PauliZ, 7), (PauliZ, 8), (PauliZ, 9)], +]; +// shor: (Pauli, Int)[][] +``` + +Example 4: +A prior distribution over phases represented as an array of double-precision real numbers. + +```qsharp +open Microsoft.Quantum.Arrays; +open Microsoft.Quantum.Convert; + +// suppose likelihood is a function of type Int -> Double. +let nPoints = 1000; +let prior = ConstantArray(nPoints, 1.0 / IntAsDouble(nPoints)); +mutable posterior = prior; +for ((idx, pr) in Enumerated(posterior)) { + set posterior w/= idx <- posterior[idx] * likelihood(idx); +} +set posterior = PNormalized(posterior, 1.0); +``` + +Example 5: +A function that returns an elementary matrix (that is, a matrix with a one in the (𝑖, 𝑗)th element and zeros elsewhere). + +```qsharp +open Microsoft.Quantum.Arrays; + +function ElementaryMatrix<'T>( + (nRows : Int, nColumns : Int), + (idxRow : Int, idxCol : Int), + zero : 'T, one : 'T +) : 'T[][] { + mutable matrix = ConstantArray( + nRows, + ConstantArray(nCols, zero) + ); + return matrix w/ row <- (matrix[row] w/ col <- one); +} + +let e01 = ElementaryMatrix((2, 2), (0, 1), 0.0, 1.0); +// e01: Double[][] = [[0.0, 1.0], [0.0, 0.0]] +``` + +## Proposed Modification + +Building on the utility of 1-D array notation, this suggestion proposes modifying Q# to include new multidimensional array types `[|'T`|], `[||'T||]`, and so forth. Like values of type `'T[]`, these new multidimensional would also be immutable, and could be manipulated by using the subscript (`[]`) and copy-and-update (`w/`) operators. + +For any type `'T`, this proposal introduces a new two-dimensional array type `[|'T|]`, a three-dimensional array type `[||'T||]`, and so forth. +For consistency with these new types, this proposal also introduces `['T]` as alternative notation for `'T[]`; we will use new notation for `'T[]` in the remainder of the proposal. + +New values of type `[|'T|]` can be written as literals using `[ [| ... |] ]` delimiters instead of `[]`, while literals of type `[||'T||]` can be written using `[ [| [| ... |] |] ]` delimiters (see example 1 below). +The `[|` and `|]` delimeters can be thought of as denoting a rectangular grid, and as constraining one level of indexing to be rectangular. + +Within multidimensional array literals, it is a _compile-time_ error to declare jagged subarrays, such as in Example 1, below. +It is similarly a compile-time error to use a non-literal array expression for part of a mutlidimensional array literal, as is shown in Example 3, below. + +Elements of a value of type `[|'T|]` can be retrieved using the subscript operator `[]` with a value of type `(Int, Int)` as the index, as in `data[(0, 1)]`. +For brevity, the parentheses marking the tuple can be dropped in this case, such that `data[(0, 1)]` and `data[0, 1]` are completely equivalent. +Similarly, elements of a value of type `[||'T||]` can be retrieved by subscripting with indices of type `(Int, Int, Int)`. +Multidimensional indices can also be used with the copy-and-update operator (`w/`) to replace elements of multidimensional arrays, as shown in Example 4 below. +Note that in the case of `w/` operators, the `()` around index tuples cannot be dropped. + +As with one-dimensional arrays, multidimensional arrays can also be subscripted by ranges. +Each axis of a multidimensional arrays can be sliced by _either_ a value of type `Range` or a value of type `Int`; for example, `(Int, Int)`, `(Range, Int)`, `(Int, Range)`, and `(Range, Range)` are valid subscripts for a value of type `[|'T|]`. +As shown in Example 5 below, for each `Int` in an index tuple, the dimensionality (aka rank) of the array is reduced by one. +That is, indexing a `[|'T|]` by `(Range, Range)` returns a rank-2 array (`[|'T|]`), while indexing by `(Int, Range)` or `(Range, Int)` returns an ordinary rank-1 array (`[|'T|]`). +Just as with indices like `(Int, Int)` and `(Int, Int, Int)`, subscripts that return slices can also be used in copy-and-replace expressions, as shown in Example 6. +When using `Range` values to index one or more axes in a multidimensional array, `...` is shorthand the `Range` value `0..1..(n - 1)` where `n` is the length of the axes being indexed. + +When used in `for` loops, multidimensional arrays iterate "on the left," yielding loop variables of one rank lower than the array being looped over, as shown in Example 7, below. + +Finally, to support multidimensional arrays, this proposal also suggests extending the `Microsoft.Quantum.Arrays` namespace with the following functions that can be used to implement libraries for working with multidimensional arrays: + +- `internal function AsNDArray2<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [|'TElement|]` +- `internal function AsNDArray3<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [||'TElement||]` +- `internal function AsNDArray4<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [|||'TElement|||]` +- `internal function AsNDArray5<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [||||'TElement||||]` +- `internal function AsNDArray6<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [|||||'TElement|||||]` +- `internal function AsNDArray7<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [||||||'TElement||||||]` +- `internal function AsNDArray8<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [|||||||'TElement|||||||]` +- `internal function NDArrayData<'TElement, 'TArray>(data : 'TArray) : ['TElement]` +- `internal function NDArrayStrides<'TArray>(data : 'TArray) : [Int]` +- `internal function NDArrayShape<'TArray>(data : 'TArray) : [Int]` +- `internal function NDArrayOffset<'TArray>(data : 'TArray) : Int` + +Each of these five functions would be `body intrinsic;`, and together form the contract between the runtime and the core Q# libraries needed to support this proposed feature (see Example 7, below). By necessity, these functions are "unsafe," in that direct access to these functions would allow violating invariants of multidimensional arrays or bypass the type system to expose runtime failures, necessitating the `internal` modifier. + +### Examples + +Example 1: +Declaring and indexing into variables of type `[|Double|]` and `[||Int||]` using literals. + +```qsharp +let z = [ + // Inside [] delimiters, [| |] delimiters refer not to array expressions, but + // to "rows" of the two-dimensional array literal. + [| 1.0, 0.0 |], + [| 0.0, -1.0 |] +]; +Message($"{z[0, 1]}"); // ← 0.0 + +// It is a compile-time error for the "rows" denoted by [| ... |] to be uneven: +let incorrect = [ + [| 1.0, 2.0 |], + [| 10.0, 20.0, 30.0 |] // ← error, since this would declare a "ragged" array +]; + +let data = [ + // We can nest two levels of [| ... |] to get three-dimensional arrays. + [| + [|0, 1|], + [|2, 3|] + |], + + [| + [|4, 5|], + [|6, 7|] + |] +]; +Message($"{data[0, 1, 0]}"); // ← 6 +``` + +Example 2: +Mixing 1D and multidimensional arrays. + +```qsharp +// Here, we declare the first two levels of indexing as being rectangular, +// resulting in a three-dimensional array. +let data = [ + [| + // After rows, [] denote arrays as elements again. + [| [0], [1, 2] |], + [| [3, 4, 5], [6, 7, 8, 9] |] + |], + + [| + [| [10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20] |], + [| [21, 22, 23, 24, 25, 26, 27], [28, 29, 30, 31, 32, 33, 34, 35] |] + |] +]; +// data: [||[Int]||] (that is, a three-dimensional array of arrays of integers) +// Get the zero-th "plane," first "row", zeroth "column," and third element. +Message($"{data[0, 1, 0][2]}"); // ← 5 +``` + +Example 3: +Using expressions as subarrays of multidimensional arrays results in a compile-time error. + +```qsharp +let a = [2, 3]; +// The following is a compile-time error, since `a` is not a 1D array literal +// of length 2. +let data = [ + [| 0, 1 |], + a +]; +// Using a new library function that can `fail` at runtime works, however. +let data = Concatenated(0, // concatenate along the 0th (row) axis + [ [| 0, 1 |] ], + a +); +// data: [|Int|] = [ +// [|0, 1|], +// [|2, 3|] +// ]; +``` + +Example 4: +Using the copy-and-replace operator to manipulate multidimensional arrays. + +```qsharp +function ElementaryMatrix( + (nRows : Int, nCols : Int), (idxRow : Int, idxCol : Int) +) : [|Double|] { + return ConstantArray2((nRows, nCols), 0.0) w/ (idxRow, idxCol) <- 1.0; +} +``` + +Example 5: +Slicing multidimensional arrays by ranges. + +```qsharp +let data = [ + [|0, 1, 2|], + [|3, 4, 5|], + [|6, 7, 8|] +]; + +// Slicing an index by a Range does not reduce the dimensionality +// of the resulting array. +let corners = data[0..2..2, 0..2..2]; +// corners: [|Int|] = [[|0, 2|], [|6, 8|]] + +// Subscripting by an Int reduces the dimensionality of the resulting array; +// here, since our index has one Int, the dimensionality reduces from +// [|Int|] to [Int]. +let firstRow = data[0, ...]; +// firstRow: [Int] = [0, 1, 2] +// The same pattern holds no matter which index we subscript with an Int. +let firstColumn = data[..., 0]; +// firstColumn = [0, 3, 6] + +let data3 = [ + [| + [|0, 1, 2|], + [|3, 4, 5|], + [|6, 7, 8|] + |], + + [| + [|9, 10, 11|], + [|12, 13, 14|], + [|15, 16, 17|] + |], + + [| + [|18, 19, 20|], + [|21, 22, 23|], + [|24, 25, 26|] + |] +]; +let corners3 = data3[0..2..2, 0..2..2, 0..2..2]; +// corners3: [||Int||] +let firstPlane = data3[0, ..., ...]; +// firstPlane: [|Int|] +let firstRowOfFirstPlane = data3[0, 0, ...]; +// firstRowOfFirstPlane: [Int] = [0, 1, 2] +``` + +Example 6: +Using multidimensional slices in copy-and-update expressions. + +```qsharp +let zeros = ConstantArray2((3, 3), 0); + +let withCorners = zeros w/ (0..2..2, 0..2..2) <- #[[1, 2], [3, 4]]; +// withCorners: [|Int|] = [ [|1, 0, 2|], [|0, 0, 0|], [|3, 0, 4|] ] + +let withFirstRow = zeros w/ (0, 0..2) <- [1, 2, 3]; +// withFirstRow: [|Int|] = [ [|1, 2, 3|], [|0, 0, 0|], [|0, 0, 0|] ] + +let withFirstColumn = zeros w/ (0..2, 0) <- [1, 2, 3]; +// withFirstColumn: [|Int|] = [ [|1, 0, 0|], [|2, 0, 0|], [|3, 0, 0|] ] +``` + +Example 7: +Iterating over multidimensional arrays. + +```qsharp +let data3 = [ + [| + [|0, 1, 2|], + [|3, 4, 5|], + [|6, 7, 8|] + |], + + [| + [|9, 10, 11|], + [|12, 13, 14|], + [|15, 16, 17|] + |], + + [| + [|18, 19, 20|], + [|21, 22, 23|], + [|24, 25, 26|] + |] +]; +// data3: [||Int||] + +for (plane in data) { + // plane: [|Int|] + for (row in plane) { + // row: [Int] + Message($"{row}"); + } + + Message(""); +} +// Output +// ====== +// [0, 1, 2] +// [3, 4, 5] +// [6, 7, 8] +// +// [9, 10, 11] +// [12, 13, 14] +// [15, 16, 17] +// +// [18, 19, 20] +// [21, 22, 23] +// [24, 25, 26] +``` + +Example 7: +Implementing array library functions using internal functions. + +```qsharp +namespace Microsoft.Quantum.Arrays { + open Microsoft.Quantum.Diagnostics; + + function Transposed2<'T>(array : [|'T|]) : [|'T|] { + // Start by deconstructing the input using the internal intrinsic + // functions from this proposal; see Implementation below. + let data = NDArrayData<'T, [|'T|]>(array); + let strides = NDArrayStrides(array); + let offset = NDArrayOffset(array); + let shape = NDArrayShape(array); + + // Now use AsNDArray2 to reconstruct, but with shape and strides + // reversed. + return AsNDArray2(data, strides, offset, shape); + } + + function ConstantArray2<'T>(shape : (Int, Int), element : 'T) : [|'T|] { + Fact(Fst(shape) >= 0, "First axis had negative length."); + Fact(Snd(shape) >= 0, "Second axis had negative length."); + + // Here, we set a stride of zero to store everything as a single + // element. Using the copy-and-update operator will require actually + // allocating the whole array, but we can start off by "cheating." + return AsNDArray2([element], [0, 0], 0, shape); + } + + function Shape2<'T>(array : [|'T|]) : (Int, Int) { + let shape = NDArrayShape(array); + return (shape[0], shape[1]); + } + + function TimesD2(left : [|Double|], right : [|Double|]) : [|Double|] { + // For simplicity, we presume that left and right already match each + // other's shape exactly. In an actual library implementation, we would + // want to generalize this to allow arbitrary binary operators, and to + // handle broadcasting between the two inputs. + + mutable data = []; + let (nRows, nCols) = Shape2(left); + for idxCol in 0..nCols - 1 { + for idxRow in 0..nRows - 1 { + set data += [left[(idxRow, idxCol)] * right[(idxRow, idxCol)]]; + } + } + return AsNDArray2(data, [1, nRows], 0, shape); + } +} +``` + +# Implementation + +**NB: Code samples in this section are intended as pseudocode only, and may not work directly as written.** + +This proposal can be implemented with minimal new data structures in the Q# runtime, using an approach similar to the [NumPy library for Python](http://numpy.scipy.org/), the [NumSharp library for C#](https://github.com/SciSharp/NumSharp), or the [ndarray library for Rust](https://github.com/rust-ndarray/ndarray/issues/597). + +In particular, each of these libraries represents multidimensional arrays by a data structure similar to the following: + +```qsharp +newtype MultidimensionalArray<'T> = ( + Data: ['T], + Offset: Int, + Shape: [Int], + Strides: [Int] +); +``` + +Together, `::Offset` and `::Strides` specify how to transform multidimensional indices into a linear index for use with `::Data`: + +```qsharp +array[i, j, k] +// implemented by: +array::Data[array::Offset + i * array::Strides[0] + j * array::Strides[1] + k * array::Data[2]] +``` + +This allows for many common array manipulations to be performed without copying. +For example, a function implementing a matrix transpose need not modify `::Data`, but can reverse `::Strides` and `::Shape`: + +```qsharp +function Transposed2<'T>(array : [|'T|]) : [|'T|] { + return array + w/ Strides <- array::Strides[...-1...] + w/ Shape <- array::Shape[...-1...]; +} +``` + +Similarly, `array[..., 0..2...]` can be implemented by doubling `::Strides[1]`: + +```qsharp +array[..., 0..2...] +// implemented by: +array + w/ Strides <- [array::Strides[0], 2 * array::Strides[1]] + w/ Shape <- [array::Shape[0], array::Shape[1] / 2] +``` + +Reversing an axis can be implemented by using negative strides and modifications to `::Offset`. + +By implementing multidimensional arrays in this way, we can reuse the existing infrastructure for immutable single-dimensional arrays. +Moreover, in many cases, multiple distinct multidimensional arrays can share the same `::Data` item without requiring a copy. +For example, in the variable binding `let view = array[0..2..., 0..2...];`, `view::Data` and `array::Data` can be the same single-dimensional array, as the difference between `view` and `data` can be expressed by only modifying `::Strides` and `::Shape`. +The cases where copies may still be required are when reshaping from more indices to less, when using `w/` to update slices, or if a particular intrinsic function or operation is implemented in simulation by interoperating with native libraries such as BLAS and LAPACK. + +## Timeline + +This proposal does not depend on any other proposals (though it can be made easier-to-use in combination with other proposals; see _Anticipated Interactions with Future Modifications_, below). + +Required implementation steps: + +- Adapt compiler to recognize new types, subscript expressions, copy-and-replace expressions, and new literal expressions. +- Implement new data structures in QIR and simulation runtime to represent values of new type, and fundamental operations on new type (e.g.: `w/` expressions). +- Design, review, and approve API design for extensions to the `Microsoft.Quantum.Arrays namespace` to support the new feature. +- Implement new library functions from previous step. +- Document new multidimensional arrays in language specification and conceptual documentation. + +# Further Considerations + +## Related Mechanisms + +This proposal generalizes the existing array feature in Q#. +As such, the new features introduced by this proposal are designed to keep to user expectations formed by existing features. +In particular: + +- Like existing arrays, multidimensional arrays are immutable values. +- Mutability can be handled using `set ... w/= ...;` statements in the same fashion as existing 1D arrays. +- Multidimensional arrays can be used as collections in loops. +- There are no subtyping relationships between array types. In particular, `[|'T|]` is not a subtype of `'T[][]` but a distinct type in its own right; nor is `('T => Unit is Adj)[,]` a subtype of `('T => Unit)[,]`. + +## Impact on Existing Mechanisms + +This proposal would not modify or invalidate existing functionality (e.g.: `Int[][]` will continue to be a valid type in Q#), and thus is not a breaking change. + +If, in a future proposal, we were to unify multidimensional array with existing functionality using features outlined in _Anticipated Interactions with Future Modifications_, a breaking change would likely be required at that point in time. + +## Anticipated Interactions with Future Modifications + +### Array comprehensions + +This proposal is expected to be compatible with array comprehensions, as the comprehension expressions can be provided at each level of nesting. + +For example, using a possible array comprehension feature, the following two statements could be equivalent: + +```qsharp +let arr = [ | x, x + 1, x + 2 | for x in 0..3 ]; +let arr = [ | 0, 1, 2 |, | 1, 2, 3 |, | 2, 3, 4 |, | 3, 4, 5 | ]; +``` + +Similarly, multiple levels of array comprehension would be compatible with the literal syntax proposed here: + +```qsharp +let arr = [ | x + y for y in 0..2 | for x in 0..3 ]; +``` + +### Handling runtime failure modalities + +Some of the type conversions described above can fail at runtime, decreasing the safety of Q# programs. +To assist, the discriminated union and type-parameterized UDT feature suggestions (https://github.com/microsoft/qsharp-compiler/issues/406) could be used to represent the possibility of runtime failures in a typesafe fashion. + +For example, the `JaggedAsRectangular2` function above could fail if its input is not actually rectangular. +Using `Maybe<'T>`, we could represent this directly: + +```qsharp +function MaybeJaggedAsRectangular2<'T>(input : [['T]]) : Maybe<'T[,]> { + if (IsRectangular2(input)) { + // convert here + } else { + return Maybe<[|'T|]>::None(); + } +} +``` + +If in a particular application, it is definitely the case that a given jagged array can be converted, the `Maybe` can be unwrapped using a function that attempts to match on values of type `Maybe<'T>`: + +```qsharp +function Forced<'T>(maybe : Maybe<'T>) : 'T { + return maybe match { + Some(value) -> value, + None() -> fail "was None()" + }; +} +``` + +### Removing type suffixes with bounded polymorphism + +Were the bounded polymorphism feature suggested at https://github.com/microsoft/qsharp-compiler/issues/557 to be adopted, the different "overloads" for the library functions suggested in this proposal could be consolidated into a smaller number of concepts that then get implemented by each of `'T[,]`, `'T[,,]`, and so forth. + +For example: + +```qsharp +// We define a new concept to represent subscripting a +// value by an index. +concept 'Array is IndexableBy<'Index, 'Element> when { + function ElementAt(index : 'Index, array : 'Array) : 'Element; +} +// We can then use that concept to explain that +// ['T] is indexed by an Int to return a 'T in the +// same way that [|'T|] is indexed by (Int, Int). +example <'T> ['T] is IndexableBy { + function ElementAt(index : Int, array : [|'T|]) { + return array[index]; + } +} +example <'T> [|'T|] is IndexableBy<(Int, Int), 'T> { + function ElementAt(index : (Int, Int), array : [|'T|]) { + return array[index]; + } +} +example <'T> [||'T||] is IndexableBy<(Int, Int, Int), 'T> { + function ElementAt(index : (Int, Int, Int), array : [|'T|]) { + return array[index]; + } +} +// This allows us to use the concept to write out more +// general functions acting on arrays of different +// dimensionality. +function Subarray<'Array, 'Index, 'Element where 'Array is IndexableBy<'Index, 'Element>>( + array : 'Array, + indices : ['Index] +) : 'Element[] { + mutable subarray = EmptyArray<'Element>(); + for (index in indices) { + // We can use ElementAt to handle + // arrays of different dimensionality in + // a uniform fashion. + set subarray += ElementAt(index, array); + } + return subarray; +} + +// Similarly, if we use type-parameterized UDTs and +// discriminated unions, we can define a concept that +// specifies when a value of a given type can possibly +// be converted to another type. +concept 'TOutput is MaybeConvertableFrom<'TInput> when { + function MaybeAs(input : 'TInput) : Maybe<'TOutput>; +} +// With that concept in place, we can say that some jagged +// arrays can be converted to rectangular arrays, obviating +// the need for JaggedAsRectangular2, JaggedAsRectangular3, +// and so forth. +example <'T> [|'T|] is MaybeConvertableFrom<[['T]]> when { + function MaybeAs(input : [['T]]]) : [|'T|] { + body intrinsic; + } +} +example <'T> [||'T||]] is MaybeConvertableFrom<[[['T]]]> when { + function MaybeAs(input : [[['T]]]]) : [||'T||] { + body intrinsic; + } +} +``` + +## Alternatives + +### Syntactic Sugar for Jagged Arrays + +As an alternative, one could consider not adding any new types to Q#, but rather extending `w/` to act on values of type `'T[][]`, `'T[][][]` using tuple indices: + +```qsharp +// Construct the elementary matrix 𝑒₀₁ = |0⟩⟨1| as the jagged array +// [[0, 1, 0], [0, 0, 0], [0, 0, 0]]. +let e01 = ConstantArray(3, ConstantArray(3, 0.0)) w/ (0, 1) <- 1.0; +``` + +### Alternative Syntax for Multidimensional Literals + +As an alternative, one could consider keeping the new types and index expressions suggested by this proposal, but modifying the definition of multidimensional literals. + +For example, alternative delimiters could be used to separate rows, planes, and so forth: + +```qsharp +let data3 = [ + 0, 1; // Separate rows with ; + 2, 3;; // Separate planes with ;; + + 4, 5; + 6, 7 +]; +// data3: [||Int||] +``` + +Other delimiters than `;` could be considered to avoid overloading statement separators. +For example, it may be considered less confusing to reuse the `|` character from `?|` expressions or the `\` character from string escapes: + +```qsharp +let x = [ + 0.0, 1.0 | + 1.0, 0.0 +]; + +let z = [ + 1.0, 0.0 \\ // LaTeX-style notation + 0.0, -1.0 +]; +``` + +These alternatives can possibly be combined with `[| |]`: + +```qsharp +let bellTableau = [ + true, true, false, false; // separate rows instead of nesting + false, false, true, true +]; +``` + +## Comparison to Alternatives + +### Comparison to Syntactic Sugar for Jagged Arrays + +While providing syntactic sugar for copy-and-update operations on jagged arrays helps address some of the more severe pain points in using jagged arrays to represent multidimensional data, that alternative does not address a few critical points: + +- Multidimensional indices cannot efficiently be converted into linear indices, causing performance problems with common matrix and tensor operations. +- Jagged arrays do not guarantee at compile time that data is rectangular, introducing the possibility of runtime logic errors with respect to the shape of multidimensional data. + +# Raised Concerns + +Any concerns about the proposed modification will be listed here and can be addressed in the [Response](#response) section below. + +## Response + From 55045e1b12b171d306c2684f4d24ef6c1df4a70b Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Fri, 26 Feb 2021 19:14:19 -0800 Subject: [PATCH 06/33] Added relationship to QEP2. --- Approved/3-multidimensional-arrays.md | 3 + Approved/multidimensional-arrays.md | 684 -------------------------- 2 files changed, 3 insertions(+), 684 deletions(-) delete mode 100644 Approved/multidimensional-arrays.md diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 2f56647..8d82672 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -135,6 +135,9 @@ The `[|` and `|]` delimeters can be thought of as denoting a rectangular grid, a Within multidimensional array literals, it is a _compile-time_ error to declare jagged subarrays, such as in Example 1, below. It is similarly a compile-time error to use a non-literal array expression for part of a mutlidimensional array literal, as is shown in Example 3, below. +Multidimensional arrays can also be created using extensions of the functionality in QEP 2. +For example, `[| element, size=(2, 3) |]` is equivalent to `[ [|element, element, element|], [|element, element, element|] ]`. + Elements of a value of type `[|'T|]` can be retrieved using the subscript operator `[]` with a value of type `(Int, Int)` as the index, as in `data[(0, 1)]`. For brevity, the parentheses marking the tuple can be dropped in this case, such that `data[(0, 1)]` and `data[0, 1]` are completely equivalent. Similarly, elements of a value of type `[||'T||]` can be retrieved by subscripting with indices of type `(Int, Int, Int)`. diff --git a/Approved/multidimensional-arrays.md b/Approved/multidimensional-arrays.md deleted file mode 100644 index 55711e4..0000000 --- a/Approved/multidimensional-arrays.md +++ /dev/null @@ -1,684 +0,0 @@ ---- -title: Multidimensional arrays -description: Proposal for multidimensional array types in Q# -author: cgranade -date: 29-10-2020 ---- - -# Proposal - -Currently, for any Q# type `'T`, the array type `'T[]` represents an immutable collection of values of type `'T` indexed by a single integer. It would be really helpful to add a new collection type that is indexed by tuples of integers instead, so as to allow for a more natural representation of concepts like matrices and tensors. - -# Justification - -Arrays of arrays as currently supported in Q# can be used to represent matrices and higher-rank tensors, but come with a number of disadvantages as compared with multidimensional arrays: - -- Nested arrays can in general be jagged; having multidimensional arrays as a type can help enforce that arrays are rectangular at the type level. -- Performance: because nested arrays can be jagged, element lookup cannot in general be done as efficiently as with linear indexing. -- Copy-and-update expressions: Using the `w/` operator to modify elements of nested arrays can be awkward. - ```qsharp - mutable matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; - set matrix w/ 2 <- (matrix[2] w/ 1 <- 20); - // matrix is now [[1, 0, 0], [0, 1, 0], [0, 20, 1]] - ``` - -# Description - -## Current Status - -Arrays types in Q# can be constructed from any element type `'T` as `'T[]`, including arrays of arrays such as `Int[][]`. -These *jagged arrays* can be used to represent multidimensional arrays of data (informally, _tensors_), such as matrices or vectors. - -While jagged arrays are extremely useful in many contexts, such as representing sparse arrays (e.g.: the "list-of-lists" representation), using jagged arrays to represent vectors, matrices, and tensors requires extensive checking of array bounds to prevent mistakes in the shapes of jagged arrays. - -### Examples - -Example 1: -Representing a complex-valued matrix with jagged arrays. - -```qsharp -open Microsoft.Quantum.Math; - -let y = [ - [Complex(0.0, 0.0), Complex(0.0, -1.0)], - [Complex(0.0, 1.0), Complex(0.0, 0.0)] -]; -// y: Complex[][] -let upperRight = y[0][1]; -// upperRight: Complex = Complex(0.0, -1.0); -``` - -Example 2: -Representing the generators of a stabilizer group as dense arrays of Pauli operators. - -```qsharp -open Microsoft.Quantum.Arrays; - -let perfect = [ - [PauliI, PauliX, PauliZ, PauliZ, PauliX], - [PauliX, PauliZ, PauliZ, PauliX, PauliI], - [PauliZ, PauliZ, PauliX, PauliI, PauliX], - [PauliZ, PauliX, PauliI, PauliX, PauliZ] -]; -// perfect: Pauli[][] -let syndrome = ForEach(Measure(_, register), perfect); -// syndrome: Result[] -``` - -Example 3: -Representing the generators of a stabilizer group as a sparse array of Pauli operators and their indices. - -```qsharp -let shor = [ - [(PauliX, 0), (PauliX, 1)], - [(PauliX, 1), (PauliX, 2)], - [(PauliX, 3), (PauliX, 4)], - [(PauliX, 4), (PauliX, 5)], - [(PauliX, 6), (PauliX, 7)], - [(PauliX, 7), (PauliX, 8)], - - [(PauliZ, 0), (PauliZ, 1), (PauliZ, 2), (PauliZ, 3), (PauliZ, 4), (PauliZ, 6)], - [(PauliZ, 3), (PauliZ, 4), (PauliZ, 6), (PauliZ, 7), (PauliZ, 8), (PauliZ, 9)], -]; -// shor: (Pauli, Int)[][] -``` - -Example 4: -A prior distribution over phases represented as an array of double-precision real numbers. - -```qsharp -open Microsoft.Quantum.Arrays; -open Microsoft.Quantum.Convert; - -// suppose likelihood is a function of type Int -> Double. -let nPoints = 1000; -let prior = ConstantArray(nPoints, 1.0 / IntAsDouble(nPoints)); -mutable posterior = prior; -for ((idx, pr) in Enumerated(posterior)) { - set posterior w/= idx <- posterior[idx] * likelihood(idx); -} -set posterior = PNormalized(posterior, 1.0); -``` - -Example 5: -A function that returns an elementary matrix (that is, a matrix with a one in the (𝑖, 𝑗)th element and zeros elsewhere). - -```qsharp -open Microsoft.Quantum.Arrays; - -function ElementaryMatrix<'T>( - (nRows : Int, nColumns : Int), - (idxRow : Int, idxCol : Int), - zero : 'T, one : 'T -) : 'T[][] { - mutable matrix = ConstantArray( - nRows, - ConstantArray(nCols, zero) - ); - return matrix w/ row <- (matrix[row] w/ col <- one); -} - -let e01 = ElementaryMatrix((2, 2), (0, 1), 0.0, 1.0); -// e01: Double[][] = [[0.0, 1.0], [0.0, 0.0]] -``` - -## Proposed Modification - -Building on the utility of 1-D array notation, this suggestion proposes modifying Q# to include new multidimensional array types `'T[,]`, `'T[,,]`, and so forth. Like values of type `'T[]`, these new multidimensional would also be immutable, and could be manipulated by using the subscript (`[]`) and copy-and-update (`w/`) operators. - -For any type `'T`, this proposal introduces a new two-dimensional array type `'T[,]`, a three-dimensional array type `'T[,,]`, and so forth. - -New values of type `'T[,]` can be written as literals using `#[]` delimiters instead of `[]`, while literals of type `'T[,,]` can be written using `##[]` delimiters (see example 1 below). -The `#` character can be thought of as denoting a rectangular grid, and as constraining one level of indexing to be rectangular. - -Within multidimensional array literals, it is a _compile-time_ error to declare jagged subarrays, such as in Example 1, below. -It is similarly a compile-time error to use a non-literal array expression for part of a mutlidimensional array literal, as is shown in Example 3, below. - -Elements of a value of type `'T[,]` can be retrieved using the subscript operator `[]` with a value of type `(Int, Int)` as the index, as in `data[(0, 1)]`. -For brevity, the parentheses marking the tuple can be dropped in this case, such that `data[(0, 1)]` and `data[0, 1]` are completely equivalent. -Similarly, elements of a value of type `'T[,,]` can be retrieved by subscripting with indices of type `(Int, Int, Int)`. -Multidimensional indices can also be used with the copy-and-update operator (`w/`) to replace elements of multidimensional arrays, as shown in Example 4 below. - -As with one-dimensional arrays, multidimensional arrays can also be subscripted by ranges. -Each axis of a multidimensional arrays can be sliced by _either_ a value of type `Range` or a value of type `Int`; for example, `(Int, Int)`, `(Range, Int)`, `(Int, Range)`, and `(Range, Range)` are valid subscripts for a value of type `'T[,]`. -As shown in Example 5 below, for each `Int` in an index tuple, the dimensionality (aka rank) of the array is reduced by one. -That is, indexing a `'T[,]` by `(Range, Range)` returns a rank-2 array (`'T[,]`), while indexing by `(Int, Range)` or `(Range, Int)` returns an ordinary rank-1 array (`'T[]`). -Just as with indices like `(Int, Int)` and `(Int, Int, Int)`, subscripts that return slices can also be used in copy-and-replace expressions, as shown in Example 6. -When using `Range` values to index one or more axes in a multidimensional array, `...` is shorthand the `Range` value `0..1..(n - 1)` where `n` is the length of the axes being indexed. - -When used in `for` loops, multidimensional arrays iterate "on the left," yielding loop variables of one rank lower than the array being looped over, as shown in Example 7, below. - -Finally, to support multidimensional arrays, this proposal also suggests extending the `Microsoft.Quantum.Arrays` namespace with the following functions that can be used to implement libraries for working with multidimensional arrays: - -- `internal function AsNDArray2<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,]` -- `internal function AsNDArray3<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,]` -- `internal function AsNDArray4<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,,]` -- `internal function AsNDArray5<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,,,]` -- `internal function AsNDArray6<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,,,,]` -- `internal function AsNDArray7<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,,,,,]` -- `internal function AsNDArray8<'TElement>(data : 'TElement[], strides : Int[], offset : Int, shape : Int[]) : 'TElement[,,,,,,,]` -- `internal function NDArrayData<'TElement, 'TArray>(data : 'TArray) : 'TElement[]` -- `internal function NDArrayStrides<'TArray>(data : 'TArray) : Int[]` -- `internal function NDArrayShape<'TArray>(data : 'TArray) : Int[]` -- `internal function NDArrayOffset<'TArray>(data : 'TArray) : Int` - -Each of these five functions would be `body intrinsic;`, and together form the contract between the runtime and the core Q# libraries needed to support this proposed feature (see Example 7, below). By necessity, these functions are "unsafe," in that direct access to these functions would allow violating invariants of multidimensional arrays or bypass the type system to expose runtime failures, necessitating the `internal` modifier. - -### Examples - -Example 1: -Declaring and indexing into variables of type `Double[,]` and `Int[,,]` using literals. - -```qsharp -let z = #[ // ← #[] marks a two-dimensional array instead of an array of arrays. - // Inside #[] delimiters, [] delimiters refer not to array expressions, but - // to "rows" of the two-dimensional array literal. - [1.0, 0.0], - [0.0, -1.0] -]; -Message($"{z[0, 1]}"); // ← 0.0 - -// Inside of #[] delimiters, it is a compile-time error for the "rows" to be -// uneven: -let incorrect = #[ - [1.0, 2.0], - [10.0, 20.0, 30.0] // ← error, since this would declare a "ragged" array -]; - -let data = ##[ // ← ##[] marks a three-dimensional array - // Inside ##[] delimiters, [] refer to "planes", then to "rows." - [ - [0, 1], - [2, 3] - ], - - [ - [4, 5], - [6, 7] - ] -]; -Message($"{data[0, 1, 0]}"); // ← 6 -``` - -Example 2: -Mixing 1D and multidimensional arrays. - -```qsharp -// Use ##[] declare the first two levels of indexing as being rectangular, -// resulting in a three-dimensional array. -let data = ##[ - // Inside ##[] delimiters, [] refer to "planes", then to "rows." - [ - // After rows, [] denote arrays as elements again. - [[0], [1, 2]], - [[3, 4, 5], [6, 7, 8, 9]] - ], - - [ - [[10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20]], - [[21, 22, 23, 24, 25, 26, 27], [28, 29, 30, 31, 32, 33, 34, 35]] - ] -]; -// data: Int[][,] (that is, a two-dimensional array of arrays of integers) -// Get the zero-th "plane," first "row", zeroth "column," and third element. -Message($"{data[0, 1, 0][2]}"); // ← 5 -``` - -Example 3: -Using expressions as subarrays of multidimensional arrays results in a compile-time error. - -```qsharp -let a = [2, 3]; -// The following is a compile-time error, since `a` is not a 1D array literal -// of length 2. -let data = #[ - [0, 1], - a -]; -// Using a new library function that can `fail` at runtime works, however. -let data = Concatenated(0, // concatenate along the 0th (row) axis - #[ [0, 1] ], - a -); -// data: Int[,] = #[ -// [0, 1], -// [2, 3] -// ]; -``` - -Example 4: -Using the copy-and-replace operator to manipulate multidimensional arrays. - -```qsharp -function ElementaryMatrix( - (nRows : Int, nCols : Int), (idxRow : Int, idxCol : Int) -) : Double[,] { - return ConstantArray2((nRows, nCols), 0.0) w/ (idxRow, idxCol) <- 1.0; -} -``` - -Example 5: -Slicing multidimensional arrays by ranges. - -```qsharp -let data = #[ - [0, 1, 2], - [3, 4, 5], - [6, 7, 8] -]; - -// Slicing an index by a Range does not reduce the dimensionality -// of the resulting array. -let corners = data[0..2..2, 0..2..2]; -// corners: Int[,] = #[[0, 2], [6, 8]] - -// Subscripting by an Int reduces the dimensionality of the resulting array; -// here, since our index has one Int, the dimensionality reduces from -// Int[,] to Int[]. -let firstRow = data[0, ...]; -// firstRow: Int[] = [0, 1, 2] -// The same pattern holds no matter which index we subscript with an Int. -let firstColumn = data[..., 0]; -// firstColumn = [0, 3, 6] - -let data3 = ##[ - [ - [0, 1, 2], - [3, 4, 5], - [6, 7, 8] - ], - - [ - [9, 10, 11], - [12, 13, 14], - [15, 16, 17] - ], - - [ - [18, 19, 20], - [21, 22, 23], - [24, 25, 26] - ] -]; -let corners3 = data3[0..2..2, 0..2..2, 0..2..2]; -// corners3: Int[,,] -let firstPlane = data3[0, ..., ...]; -// firstPlane: Int[,] -let firstRowOfFirstPlane = data3[0, 0, ...]; -// firstRowOfFirstPlane: Int[] = [0, 1, 2] -``` - -Example 6: -Using multidimensional slices in copy-and-update expressions. - -```qsharp -let zeros = ConstantArray2((3, 3), 0); - -let withCorners = zeros w/ (0..2..2, 0..2..2) <- #[[1, 2], [3, 4]]; -// withCorners: Int[,] = #[ [1, 0, 2], [0, 0, 0], [3, 0, 4]] - -let withFirstRow = zeros w/ (0, 0..2) <- [1, 2, 3]; -// withFirstRow: Int[,] = #[ [1, 2, 3], [0, 0, 0], [0, 0, 0] ] - -let withFirstColumn = zeros w/ (0..2, 0) <- [1, 2, 3]; -// withFirstColumn: Int[,] = #[ [1, 0, 0], [2, 0, 0], [3, 0, 0] ] -``` - -Example 7: -Iterating over multidimensional arrays. - -```qsharp -let data3 = ##[ - [ - [0, 1, 2], - [3, 4, 5], - [6, 7, 8] - ], - - [ - [9, 10, 11], - [12, 13, 14], - [15, 16, 17] - ], - - [ - [18, 19, 20], - [21, 22, 23], - [24, 25, 26] - ] -]; -// data3: Int[,,] - -for (plane in data) { - // plane: Int[,] - for (row in plane) { - // row: Int[] - Message($"{row}"); - } - - Message(""); -} -// Output -// ====== -// [0, 1, 2] -// [3, 4, 5] -// [6, 7, 8] -// -// [9, 10, 11] -// [12, 13, 14] -// [15, 16, 17] -// -// [18, 19, 20] -// [21, 22, 23] -// [24, 25, 26] -``` - -Example 7: -Implementing array library functions using internal functions. - -```qsharp -namespace Microsoft.Quantum.Arrays { - open Microsoft.Quantum.Diagnostics; - - function Transposed2<'T>(array : 'T[,]) : 'T[,] { - // Start by deconstructing the input using the internal intrinsic - // functions from this proposal; see Implementation below. - let data = NDArrayData<'T, 'T[,]>(array); - let strides = NDArrayStrides(array); - let offset = NDArrayOffset(array); - let shape = NDArrayShape(array); - - // Now use AsNDArray2 to reconstruct, but with shape and strides - // reversed. - return AsNDArray2(data, strides, offset, shape); - } - - function ConstantArray2<'T>(shape : (Int, Int), element : 'T) : 'T[,] { - Fact(Fst(shape) >= 0, "First axis had negative length."); - Fact(Snd(shape) >= 0, "Second axis had negative length."); - - // Here, we set a stride of zero to store everything as a single - // element. Using the copy-and-update operator will require actually - // allocating the whole array, but we can start off by "cheating." - return AsNDArray2([element], [0, 0], 0, shape); - } - - function Shape2<'T>(array : 'T[,]) : (Int, Int) { - let shape = NDArrayShape(array); - return (shape[0], shape[1]); - } - - function TimesD2(left : Double[,], right : Double[,]) : Double[,] { - // For simplicity, we presume that left and right already match each - // other's shape exactly. In an actual library implementation, we would - // want to generalize this to allow arbitrary binary operators, and to - // handle broadcasting between the two inputs. - - mutable data = []; - let (nRows, nCols) = Shape2(left); - for idxCol in 0..nCols - 1 { - for idxRow in 0..nRows - 1 { - set data += [left[(idxRow, idxCol)] * right[(idxRow, idxCol)]]; - } - } - return AsNDArray2(data, [1, nRows], 0, shape); - } -} -``` - -# Implementation - -**NB: Code samples in this section are intended as pseudocode only, and may not work directly as written.** - -This proposal can be implemented with minimal new data structures in the Q# runtime, using an approach similar to the [NumPy library for Python](http://numpy.scipy.org/), the [NumSharp library for C#](https://github.com/SciSharp/NumSharp), or the [ndarray library for Rust](https://github.com/rust-ndarray/ndarray/issues/597). - -In particular, each of these libraries represents multidimensional arrays by a data structure similar to the following: - -```qsharp -newtype MultidimensionalArray<'T> = ( - Data: 'T[], - Offset: Int, - Shape: Int[], - Strides: Int[] -); -``` - -Together, `::Offset` and `::Strides` specify how to transform multidimensional indices into a linear index for use with `::Data`: - -```qsharp -array[i, j, k] -// implemented by: -array::Data[array::Offset + i * array::Strides[0] + j * array::Strides[1] + k * array::Data[2]] -``` - -This allows for many common array manipulations to be performed without copying. -For example, a function implementing a matrix transpose need not modify `::Data`, but can reverse `::Strides` and `::Shape`: - -```qsharp -function Transposed2<'T>(array : 'T[,]) : 'T[,] { - return array - w/ Strides <- array::Strides[...-1...] - w/ Shape <- array::Shape[...-1...]; -} -``` - -Similarly, `array[..., 0..2...]` can be implemented by doubling `::Strides[1]`: - -```qsharp -array[..., 0..2...] -// implemented by: -array - w/ Strides <- [array::Strides[0], 2 * array::Strides[1]] - w/ Shape <- [array::Shape[0], array::Shape[1] / 2] -``` - -Reversing an axis can be implemented by using negative strides and modifications to `::Offset`. - -By implementing multidimensional arrays in this way, we can reuse the existing infrastructure for immutable single-dimensional arrays. -Moreover, in many cases, multiple distinct multidimensional arrays can share the same `::Data` item without requiring a copy. -For example, in the variable binding `let view = array[0..2..., 0..2...];`, `view::Data` and `array::Data` can be the same single-dimensional array, as the difference between `view` and `data` can be expressed by only modifying `::Strides` and `::Shape`. -The cases where copies may still be required are when reshaping from more indices to less, when using `w/` to update slices, or if a particular intrinsic function or operation is implemented in simulation by interoperating with native libraries such as BLAS and LAPACK. - -## Timeline - -This proposal does not depend on any other proposals (though it can be made easier-to-use in combination with other proposals; see _Anticipated Interactions with Future Modifications_, below). - -Required implementation steps: - -- Adapt compiler to recognize new types, subscript expressions, copy-and-replace expressions, and new literal expressions. -- Implement new data structures in QIR and simulation runtime to represent values of new type, and fundamental operations on new type (e.g.: `w/` expressions). -- Design, review, and approve API design for extensions to the `Microsoft.Quantum.Arrays namespace` to support the new feature. -- Implement new library functions from previous step. -- Document new multidimensional arrays in language specification and conceptual documentation. - -# Further Considerations - -## Related Mechanisms - -This proposal generalizes the existing array feature in Q#. -As such, the new features introduced by this proposal are designed to keep to user expectations formed by existing features. -In particular: - -- Like existing arrays, multidimensional arrays are immutable values. -- Mutability can be handled using `set ... w/= ...;` statements in the same fashion as existing 1D arrays. -- Multidimensional arrays can be used as collections in loops. -- There are no subtyping relationships between array types. In particular, `'T[,]` is not a subtype of `'T[][]` but a distinct type in its own right; nor is `('T => Unit is Adj)[,]` a subtype of `('T => Unit)[,]`. - -## Impact on Existing Mechanisms - -This proposal would not modify or invalidate existing functionality (e.g.: `Int[][]` will continue to be a valid type in Q#), and thus is not a breaking change. - -If, in a future proposal, we were to unify multidimensional array with existing functionality using features outlined in _Anticipated Interactions with Future Modifications_, a breaking change would likely be required at that point in time. - -## Anticipated Interactions with Future Modifications - -### Handling runtime failure modalities - -Some of the type conversions described above can fail at runtime, decreasing the safety of Q# programs. -To assist, the discriminated union and type-parameterized UDT feature suggestions (https://github.com/microsoft/qsharp-compiler/issues/406) could be used to represent the possibility of runtime failures in a typesafe fashion. - -For example, the `JaggedAsRectangular2` function above could fail if its input is not actually rectangular. -Using `Maybe<'T>`, we could represent this directly: - -```qsharp -function MaybeJaggedAsRectangular2<'T>(input : 'T[][]) : Maybe<'T[,]> { - if (IsRectangular2(input)) { - // convert here - } else { - return Maybe<'T[,]>::None(); - } -} -``` - -If in a particular application, it is definitely the case that a given jagged array can be converted, the `Maybe` can be unwrapped using a function that attempts to match on values of type `Maybe<'T>`: - -```qsharp -function Forced<'T>(maybe : Maybe<'T>) : 'T { - return maybe match { - Some(value) -> value, - None() -> fail "was None()" - }; -} -``` - -### Removing type suffixes with bounded polymorphism - -Were the bounded polymorphism feature suggested at https://github.com/microsoft/qsharp-compiler/issues/557 to be adopted, the different "overloads" for the library functions suggested in this proposal could be consolidated into a smaller number of concepts that then get implemented by each of `'T[,]`, `'T[,,]`, and so forth. - -For example: - -```qsharp -// We define a new concept to represent subscripting a -// value by an index. -concept 'Array is IndexableBy<'Index, 'Element> when { - function ElementAt(index : 'Index, array : 'Array) : 'Element; -} -// We can then use that concept to explain that -// 'T[] is indexed by an Int to return a 'T in the -// same way that 'T[,] is indexed by (Int, Int). -example <'T> 'T[] is IndexableBy { - function ElementAt(index : Int, array : 'T[,]) { - return array[index]; - } -} -example <'T> 'T[,] is IndexableBy<(Int, Int), 'T> { - function ElementAt(index : (Int, Int), array : 'T[,]) { - return array[index]; - } -} -example <'T> 'T[,,] is IndexableBy<(Int, Int, Int), 'T> { - function ElementAt(index : (Int, Int, Int), array : 'T[,]) { - return array[index]; - } -} -// This allows us to use the concept to write out more -// general functions acting on arrays of different -// dimensionality. -function Subarray<'Array, 'Index, 'Element where 'Array is IndexableBy<'Index, 'Element>>( - array : 'Array, - indices : 'Index[] -) : 'Element[] { - mutable subarray = EmptyArray<'Element>(); - for (index in indices) { - // We can use ElementAt to handle - // arrays of different dimensionality in - // a uniform fashion. - set subarray += ElementAt(index, array); - } - return subarray; -} - -// Similarly, if we use type-parameterized UDTs and -// discriminated unions, we can define a concept that -// specifies when a value of a given type can possibly -// be converted to another type. -concept 'TOutput is MaybeConvertableFrom<'TInput> when { - function MaybeAs(input : 'TInput) : Maybe<'TOutput>; -} -// With that concept in place, we can say that some jagged -// arrays can be converted to rectangular arrays, obviating -// the need for JaggedAsRectangular2, JaggedAsRectangular3, -// and so forth. -example <'T> 'T[,] is MaybeConvertableFrom<'T[][]> when { - function MaybeAs(input : 'T[][]) : 'T[,] { - body intrinsic; - } -} -example <'T> 'T[,,] is MaybeConvertableFrom<'T[][][]> when { - function MaybeAs(input : 'T[][][]) : 'T[,,] { - body intrinsic; - } -} -``` - -## Alternatives - -### Syntactic Sugar for Jagged Arrays - -As an alternative, one could consider not adding any new types to Q#, but rather extending `w/` to act on values of type `'T[][]`, `'T[][][]` using tuple indices: - -```qsharp -// Construct the elementary matrix 𝑒₀₁ = |0⟩⟨1| as the jagged array -// [[0, 1, 0], [0, 0, 0], [0, 0, 0]]. -let e01 = ConstantArray(3, ConstantArray(3, 0.0)) w/ (0, 1) <- 1.0; -``` - -### Alternative Syntax for Multidimensional Literals - -As an alternative, one could consider keeping the new types and index expressions suggested by this proposal, but modifying the definition of multidimensional literals. - -For example, alternative delimiters could be used to separate rows, planes, and so forth: - -```qsharp -let data3 = [ - 0, 1; // Separate rows with ; - 2, 3;; // Separate planes with ;; - - 4, 5; - 6, 7 -]; -// data3: Int[,,] -``` - -Other delimiters than `;` could be considered to avoid overloading statement separators. -For example, it may be considered less confusing to reuse the `|` character from `?|` expressions or the `\` character from string escapes: - -```qsharp -let x = [ - 0.0, 1.0 | - 1.0, 0.0 -]; - -let z = [ - 1.0, 0.0 \\ // LaTeX-style notation - 0.0, -1.0 -]; -``` - -These alternatives can possibly be combined with `#[]`: - -```qsharp -let bellTableau = #[ // denote 2D array - true, true, false, false; // separate rows instead of nesting [] inside #[] - false, false, true, true -]; -``` - -## Comparison to Alternatives - -### Comparison to Syntactic Sugar for Jagged Arrays - -While providing syntactic sugar for copy-and-update operations on jagged arrays helps address some of the more severe pain points in using jagged arrays to represent multidimensional data, that alternative does not address a few critical points: - -- Multidimensional indices cannot efficiently be converted into linear indices, causing performance problems with common matrix and tensor operations. -- Jagged arrays do not guarantee at compile time that data is rectangular, introducing the possibility of runtime logic errors with respect to the shape of multidimensional data. - -# Raised Concerns - -Any concerns about the proposed modification will be listed here and can be addressed in the [Response](#response) section below. - -## Response - - - - From 6f0107b92b1ae2a89c295a3d7d484cac97f5612c Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Mon, 1 Mar 2021 11:49:44 -0800 Subject: [PATCH 07/33] Update Approved/3-multidimensional-arrays.md Co-authored-by: bettinaheim <34236215+bettinaheim@users.noreply.github.com> --- Approved/3-multidimensional-arrays.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 8d82672..5148e52 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -26,7 +26,7 @@ Arrays of arrays as currently supported in Q# can be used to represent matrices ## Current Status -Arrays types in Q# can be constructed from any element type `'T` as `'T[]`, including arrays of arrays such as `[Int][]`. +Arrays types in Q# can be constructed from any element type `'T` as `'T[]`, including arrays of arrays such as `Int[][]`. These *jagged arrays* can be used to represent multidimensional arrays of data (informally, _tensors_), such as matrices or vectors. While jagged arrays are extremely useful in many contexts, such as representing sparse arrays (e.g.: the "list-of-lists" representation), using jagged arrays to represent vectors, matrices, and tensors requires extensive checking of array bounds to prevent mistakes in the shapes of jagged arrays. @@ -698,4 +698,3 @@ While providing syntactic sugar for copy-and-update operations on jagged arrays Any concerns about the proposed modification will be listed here and can be addressed in the [Response](#response) section below. ## Response - From 25629a2dadb250a27bd8f8a7f79d00f6700cf979 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Mon, 1 Mar 2021 11:51:36 -0800 Subject: [PATCH 08/33] Update Approved/3-multidimensional-arrays.md Co-authored-by: bettinaheim <34236215+bettinaheim@users.noreply.github.com> --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 5148e52..b44b964 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -133,7 +133,7 @@ New values of type `[|'T|]` can be written as literals using `[ [| ... |] ]` del The `[|` and `|]` delimeters can be thought of as denoting a rectangular grid, and as constraining one level of indexing to be rectangular. Within multidimensional array literals, it is a _compile-time_ error to declare jagged subarrays, such as in Example 1, below. -It is similarly a compile-time error to use a non-literal array expression for part of a mutlidimensional array literal, as is shown in Example 3, below. +It is similarly a compile-time error to use a non-literal array expression for part of a multi-dimensional array literal, as is shown in Example 3, below. Multidimensional arrays can also be created using extensions of the functionality in QEP 2. For example, `[| element, size=(2, 3) |]` is equivalent to `[ [|element, element, element|], [|element, element, element|] ]`. From 25999c7689b2b1141493e4aa3bd4e85bb49fa2aa Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Mon, 1 Mar 2021 12:06:30 -0800 Subject: [PATCH 09/33] Apply suggestions from code review Co-authored-by: bettinaheim <34236215+bettinaheim@users.noreply.github.com> --- Approved/3-multidimensional-arrays.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index b44b964..6ac7fb6 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -141,15 +141,15 @@ For example, `[| element, size=(2, 3) |]` is equivalent to `[ [|element, element Elements of a value of type `[|'T|]` can be retrieved using the subscript operator `[]` with a value of type `(Int, Int)` as the index, as in `data[(0, 1)]`. For brevity, the parentheses marking the tuple can be dropped in this case, such that `data[(0, 1)]` and `data[0, 1]` are completely equivalent. Similarly, elements of a value of type `[||'T||]` can be retrieved by subscripting with indices of type `(Int, Int, Int)`. -Multidimensional indices can also be used with the copy-and-update operator (`w/`) to replace elements of multidimensional arrays, as shown in Example 4 below. -Note that in the case of `w/` operators, the `()` around index tuples cannot be dropped. +Multidimensional indices can also be used with the copy-and-update operator (`w/`) to create a new array that replaces one or more specific element(s) of a multidimensional array, as shown in Example 4 below. +In the case of `w/` operators, the `()` around index tuples cannot be dropped. As with one-dimensional arrays, multidimensional arrays can also be subscripted by ranges. Each axis of a multidimensional arrays can be sliced by _either_ a value of type `Range` or a value of type `Int`; for example, `(Int, Int)`, `(Range, Int)`, `(Int, Range)`, and `(Range, Range)` are valid subscripts for a value of type `[|'T|]`. As shown in Example 5 below, for each `Int` in an index tuple, the dimensionality (aka rank) of the array is reduced by one. -That is, indexing a `[|'T|]` by `(Range, Range)` returns a rank-2 array (`[|'T|]`), while indexing by `(Int, Range)` or `(Range, Int)` returns an ordinary rank-1 array (`[|'T|]`). -Just as with indices like `(Int, Int)` and `(Int, Int, Int)`, subscripts that return slices can also be used in copy-and-replace expressions, as shown in Example 6. -When using `Range` values to index one or more axes in a multidimensional array, `...` is shorthand the `Range` value `0..1..(n - 1)` where `n` is the length of the axes being indexed. +That is, indexing a `[|'T|]` by `(Range, Range)` returns a rank-2 array (`[|'T|]`), while indexing by `(Int, Range)` or `(Range, Int)` returns an ordinary rank-1 array (`['T]`). +Just as with indices like `(Int, Int)` and `(Int, Int, Int)`, subscripts that return slices can also be used in copy-and-update expressions, as shown in Example 6. +When using values of type `Range` to index one or more axes in a multidimensional array, `...` is shorthand for the value `0..1..(n - 1)` where `n` is the length of the axis being indexed. When used in `for` loops, multidimensional arrays iterate "on the left," yielding loop variables of one rank lower than the array being looped over, as shown in Example 7, below. From 434c3e871a732dde9d4129e8b37280cd127f5f68 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Mon, 1 Mar 2021 19:19:43 -0800 Subject: [PATCH 10/33] Continued addressing feedback. --- Approved/3-multidimensional-arrays.md | 58 +++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 8d82672..abcef1a 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -1,15 +1,16 @@ ---- -title: Multidimensional arrays +--- description: Proposal for multidimensional array types in Q# -author: cgranade -date: 29-10-2020 +author: Chris Granade +date: 2021-03-01 --- -# Proposal +# QEP 3: Multi-dimensional arrays + +## Proposal Currently, for any Q# type `'T`, the array type `'T[]` represents an immutable collection of values of type `'T` indexed by a single integer. It would be really helpful to add a new collection type that is indexed by tuples of integers instead, so as to allow for a more natural representation of concepts like matrices and tensors. -# Justification +## Justification Arrays of arrays as currently supported in Q# can be used to represent matrices and higher-rank tensors, but come with a number of disadvantages as compared with multidimensional arrays: @@ -22,16 +23,16 @@ Arrays of arrays as currently supported in Q# can be used to represent matrices // matrix is now [[1, 0, 0], [0, 1, 0], [0, 20, 1]] ``` -# Description +## Description -## Current Status +### Current Status Arrays types in Q# can be constructed from any element type `'T` as `'T[]`, including arrays of arrays such as `[Int][]`. These *jagged arrays* can be used to represent multidimensional arrays of data (informally, _tensors_), such as matrices or vectors. While jagged arrays are extremely useful in many contexts, such as representing sparse arrays (e.g.: the "list-of-lists" representation), using jagged arrays to represent vectors, matrices, and tensors requires extensive checking of array bounds to prevent mistakes in the shapes of jagged arrays. -### Examples +#### Examples Example 1: Representing a complex-valued matrix with jagged arrays. @@ -122,7 +123,7 @@ let e01 = ElementaryMatrix((2, 2), (0, 1), 0.0, 1.0); // e01: Double[][] = [[0.0, 1.0], [0.0, 0.0]] ``` -## Proposed Modification +### Proposed Modification Building on the utility of 1-D array notation, this suggestion proposes modifying Q# to include new multidimensional array types `[|'T`|], `[||'T||]`, and so forth. Like values of type `'T[]`, these new multidimensional would also be immutable, and could be manipulated by using the subscript (`[]`) and copy-and-update (`w/`) operators. @@ -169,7 +170,7 @@ Finally, to support multidimensional arrays, this proposal also suggests extendi Each of these five functions would be `body intrinsic;`, and together form the contract between the runtime and the core Q# libraries needed to support this proposed feature (see Example 7, below). By necessity, these functions are "unsafe," in that direct access to these functions would allow violating invariants of multidimensional arrays or bypass the type system to expose runtime failures, necessitating the `internal` modifier. -### Examples +#### Examples Example 1: Declaring and indexing into variables of type `[|Double|]` and `[||Int||]` using literals. @@ -429,7 +430,7 @@ namespace Microsoft.Quantum.Arrays { } ``` -# Implementation +## Implementation **NB: Code samples in this section are intended as pseudocode only, and may not work directly as written.** @@ -482,7 +483,7 @@ Moreover, in many cases, multiple distinct multidimensional arrays can share the For example, in the variable binding `let view = array[0..2..., 0..2...];`, `view::Data` and `array::Data` can be the same single-dimensional array, as the difference between `view` and `data` can be expressed by only modifying `::Strides` and `::Shape`. The cases where copies may still be required are when reshaping from more indices to less, when using `w/` to update slices, or if a particular intrinsic function or operation is implemented in simulation by interoperating with native libraries such as BLAS and LAPACK. -## Timeline +### Timeline This proposal does not depend on any other proposals (though it can be made easier-to-use in combination with other proposals; see _Anticipated Interactions with Future Modifications_, below). @@ -494,9 +495,9 @@ Required implementation steps: - Implement new library functions from previous step. - Document new multidimensional arrays in language specification and conceptual documentation. -# Further Considerations +## Further Considerations -## Related Mechanisms +### Related Mechanisms This proposal generalizes the existing array feature in Q#. As such, the new features introduced by this proposal are designed to keep to user expectations formed by existing features. @@ -507,15 +508,15 @@ In particular: - Multidimensional arrays can be used as collections in loops. - There are no subtyping relationships between array types. In particular, `[|'T|]` is not a subtype of `'T[][]` but a distinct type in its own right; nor is `('T => Unit is Adj)[,]` a subtype of `('T => Unit)[,]`. -## Impact on Existing Mechanisms +### Impact on Existing Mechanisms This proposal would not modify or invalidate existing functionality (e.g.: `Int[][]` will continue to be a valid type in Q#), and thus is not a breaking change. If, in a future proposal, we were to unify multidimensional array with existing functionality using features outlined in _Anticipated Interactions with Future Modifications_, a breaking change would likely be required at that point in time. -## Anticipated Interactions with Future Modifications +### Anticipated Interactions with Future Modifications -### Array comprehensions +#### Array comprehensions This proposal is expected to be compatible with array comprehensions, as the comprehension expressions can be provided at each level of nesting. @@ -532,7 +533,7 @@ Similarly, multiple levels of array comprehension would be compatible with the l let arr = [ | x + y for y in 0..2 | for x in 0..3 ]; ``` -### Handling runtime failure modalities +#### Handling runtime failure modalities Some of the type conversions described above can fail at runtime, decreasing the safety of Q# programs. To assist, the discriminated union and type-parameterized UDT feature suggestions (https://github.com/microsoft/qsharp-compiler/issues/406) could be used to represent the possibility of runtime failures in a typesafe fashion. @@ -561,7 +562,7 @@ function Forced<'T>(maybe : Maybe<'T>) : 'T { } ``` -### Removing type suffixes with bounded polymorphism +#### Removing type suffixes with bounded polymorphism Were the bounded polymorphism feature suggested at https://github.com/microsoft/qsharp-compiler/issues/557 to be adopted, the different "overloads" for the library functions suggested in this proposal could be consolidated into a smaller number of concepts that then get implemented by each of `'T[,]`, `'T[,,]`, and so forth. @@ -631,9 +632,9 @@ example <'T> [||'T||]] is MaybeConvertableFrom<[[['T]]]> when { } ``` -## Alternatives +### Alternatives -### Syntactic Sugar for Jagged Arrays +#### Syntactic Sugar for Jagged Arrays As an alternative, one could consider not adding any new types to Q#, but rather extending `w/` to act on values of type `'T[][]`, `'T[][][]` using tuple indices: @@ -643,7 +644,7 @@ As an alternative, one could consider not adding any new types to Q#, but rather let e01 = ConstantArray(3, ConstantArray(3, 0.0)) w/ (0, 1) <- 1.0; ``` -### Alternative Syntax for Multidimensional Literals +#### Alternative Syntax for Multidimensional Literals As an alternative, one could consider keeping the new types and index expressions suggested by this proposal, but modifying the definition of multidimensional literals. @@ -684,18 +685,17 @@ let bellTableau = [ ]; ``` -## Comparison to Alternatives +### Comparison to Alternatives -### Comparison to Syntactic Sugar for Jagged Arrays +#### Comparison to Syntactic Sugar for Jagged Arrays While providing syntactic sugar for copy-and-update operations on jagged arrays helps address some of the more severe pain points in using jagged arrays to represent multidimensional data, that alternative does not address a few critical points: - Multidimensional indices cannot efficiently be converted into linear indices, causing performance problems with common matrix and tensor operations. - Jagged arrays do not guarantee at compile time that data is rectangular, introducing the possibility of runtime logic errors with respect to the shape of multidimensional data. -# Raised Concerns - -Any concerns about the proposed modification will be listed here and can be addressed in the [Response](#response) section below. +## Raised Concerns -## Response +Any concerns about the proposed modification will be listed here and can be addressed in the [Response](#response) section below. +### Response From 17402d7e072da1b659f0f1b582f9d64272f99eef Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Mon, 1 Mar 2021 19:31:23 -0800 Subject: [PATCH 11/33] Apply suggestions from code review Co-authored-by: bettinaheim <34236215+bettinaheim@users.noreply.github.com> --- Approved/3-multidimensional-arrays.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index d7983c0..27fcd39 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -137,7 +137,7 @@ Within multidimensional array literals, it is a _compile-time_ error to declare It is similarly a compile-time error to use a non-literal array expression for part of a multi-dimensional array literal, as is shown in Example 3, below. Multidimensional arrays can also be created using extensions of the functionality in QEP 2. -For example, `[| element, size=(2, 3) |]` is equivalent to `[ [|element, element, element|], [|element, element, element|] ]`. +For example, `[| element, size=(2, 3) |]` is equivalent to `[|element, element, element|, |element, element, element|]`. Elements of a value of type `[|'T|]` can be retrieved using the subscript operator `[]` with a value of type `(Int, Int)` as the index, as in `data[(0, 1)]`. For brevity, the parentheses marking the tuple can be dropped in this case, such that `data[(0, 1)]` and `data[0, 1]` are completely equivalent. @@ -280,7 +280,7 @@ let corners = data[0..2..2, 0..2..2]; // here, since our index has one Int, the dimensionality reduces from // [|Int|] to [Int]. let firstRow = data[0, ...]; -// firstRow: [Int] = [0, 1, 2] +// firstRow = [0, 1, 2] // The same pattern holds no matter which index we subscript with an Int. let firstColumn = data[..., 0]; // firstColumn = [0, 3, 6] @@ -305,11 +305,11 @@ let data3 = [ |] ]; let corners3 = data3[0..2..2, 0..2..2, 0..2..2]; -// corners3: [||Int||] +// corners3 is of type [||Int||] let firstPlane = data3[0, ..., ...]; -// firstPlane: [|Int|] +// firstPlane is of type [|Int|] let firstRowOfFirstPlane = data3[0, 0, ...]; -// firstRowOfFirstPlane: [Int] = [0, 1, 2] +// firstRowOfFirstPlane = [0, 1, 2] and is of type [Int] ``` Example 6: From daefa82ff294110ad277fc03fda0f132774207df Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Mon, 1 Mar 2021 19:35:01 -0800 Subject: [PATCH 12/33] Update Approved/3-multidimensional-arrays.md Co-authored-by: bettinaheim <34236215+bettinaheim@users.noreply.github.com> --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 27fcd39..7a71108 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -130,7 +130,7 @@ Building on the utility of 1-D array notation, this suggestion proposes modifyin For any type `'T`, this proposal introduces a new two-dimensional array type `[|'T|]`, a three-dimensional array type `[||'T||]`, and so forth. For consistency with these new types, this proposal also introduces `['T]` as alternative notation for `'T[]`; we will use new notation for `'T[]` in the remainder of the proposal. -New values of type `[|'T|]` can be written as literals using `[ [| ... |] ]` delimiters instead of `[]`, while literals of type `[||'T||]` can be written using `[ [| [| ... |] |] ]` delimiters (see example 1 below). +New values of type `[|'T|]` can be written as literals using `[| ... |]` delimiters instead of `[]`, while literals of type `[||'T||]` can be written using `[|| ... ||]` delimiters (see example 1 below). The `[|` and `|]` delimeters can be thought of as denoting a rectangular grid, and as constraining one level of indexing to be rectangular. Within multidimensional array literals, it is a _compile-time_ error to declare jagged subarrays, such as in Example 1, below. From 4bc249a15cdf93791212901699615a38ae6dd31d Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Mon, 1 Mar 2021 19:45:07 -0800 Subject: [PATCH 13/33] Update Approved/3-multidimensional-arrays.md Co-authored-by: bettinaheim <34236215+bettinaheim@users.noreply.github.com> --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 7a71108..b187a98 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -316,7 +316,7 @@ Example 6: Using multidimensional slices in copy-and-update expressions. ```qsharp -let zeros = ConstantArray2((3, 3), 0); +let zeros =[|0, size = (3, 3)|]); let withCorners = zeros w/ (0..2..2, 0..2..2) <- #[[1, 2], [3, 4]]; // withCorners: [|Int|] = [ [|1, 0, 2|], [|0, 0, 0|], [|3, 0, 4|] ] From ae240581356621ea63ed8c0b3051631020d28bfe Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Tue, 2 Mar 2021 09:56:05 -0800 Subject: [PATCH 14/33] Update Approved/3-multidimensional-arrays.md Co-authored-by: bettinaheim <34236215+bettinaheim@users.noreply.github.com> --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index b187a98..4c3ebd5 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -490,7 +490,7 @@ This proposal does not depend on any other proposals (though it can be made easi Required implementation steps: - Adapt compiler to recognize new types, subscript expressions, copy-and-replace expressions, and new literal expressions. -- Implement new data structures in QIR and simulation runtime to represent values of new type, and fundamental operations on new type (e.g.: `w/` expressions). +- Implement new data structures in QIR and runtime to represent values of new type, and fundamental operations on new type (e.g.: `w/` expressions). - Design, review, and approve API design for extensions to the `Microsoft.Quantum.Arrays namespace` to support the new feature. - Implement new library functions from previous step. - Document new multidimensional arrays in language specification and conceptual documentation. From 882574f26b8c829a91d6036ffeb57fd1163b3fcf Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 2 Mar 2021 09:56:19 -0800 Subject: [PATCH 15/33] Addressing more feedback. --- Approved/3-multidimensional-arrays.md | 64 ++++++++++++++------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 7a71108..ebed103 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -131,7 +131,8 @@ For any type `'T`, this proposal introduces a new two-dimensional array type `[| For consistency with these new types, this proposal also introduces `['T]` as alternative notation for `'T[]`; we will use new notation for `'T[]` in the remainder of the proposal. New values of type `[|'T|]` can be written as literals using `[| ... |]` delimiters instead of `[]`, while literals of type `[||'T||]` can be written using `[|| ... ||]` delimiters (see example 1 below). -The `[|` and `|]` delimeters can be thought of as denoting a rectangular grid, and as constraining one level of indexing to be rectangular. +The `|` and `|` delimeters occuring within square brackets can be thought of as denoting a rectangular grid, and as constraining one level of indexing to be rectangular. +Similarly, new values of type `[|'T|]` can also be obtained by using the library functions in https://github.com/microsoft/QuantumLibraries/issues/408; e.g., `JaggedAsRectangular2([[2, 3], [4, 5]])` is equivalent to the literal `[|2, 3|, |4, 5|]`. Within multidimensional array literals, it is a _compile-time_ error to declare jagged subarrays, such as in Example 1, below. It is similarly a compile-time error to use a non-literal array expression for part of a multi-dimensional array literal, as is shown in Example 3, below. @@ -318,14 +319,14 @@ Using multidimensional slices in copy-and-update expressions. ```qsharp let zeros = ConstantArray2((3, 3), 0); -let withCorners = zeros w/ (0..2..2, 0..2..2) <- #[[1, 2], [3, 4]]; -// withCorners: [|Int|] = [ [|1, 0, 2|], [|0, 0, 0|], [|3, 0, 4|] ] +let withCorners = zeros w/ (0..2..2, 0..2..2) <- [|1, 2|, |3, 4|]; +// withCorners = [|1, 0, 2|, |0, 0, 0|, |3, 0, 4|] and is of type [|Int|]. let withFirstRow = zeros w/ (0, 0..2) <- [1, 2, 3]; -// withFirstRow: [|Int|] = [ [|1, 2, 3|], [|0, 0, 0|], [|0, 0, 0|] ] +// withFirstRow = [|1, 2, 3|, |0, 0, 0|, |0, 0, 0|] and is of type [|Int|]. let withFirstColumn = zeros w/ (0..2, 0) <- [1, 2, 3]; -// withFirstColumn: [|Int|] = [ [|1, 0, 0|], [|2, 0, 0|], [|3, 0, 0|] ] +// withFirstColumn = [|1, 0, 0|, |2, 0, 0|, |3, 0, 0|] and is of type [|Int|] ``` Example 7: @@ -333,30 +334,30 @@ Iterating over multidimensional arrays. ```qsharp let data3 = [ - [| - [|0, 1, 2|], - [|3, 4, 5|], - [|6, 7, 8|] - |], - - [| - [|9, 10, 11|], - [|12, 13, 14|], - [|15, 16, 17|] - |], - - [| - [|18, 19, 20|], - [|21, 22, 23|], - [|24, 25, 26|] - |] + | + |0, 1, 2|, + |3, 4, 5|, + |6, 7, 8| + |, + + | + |9, 10, 11|, + |12, 13, 14|, + |15, 16, 17| + |, + + | + |18, 19, 20|, + |21, 22, 23|, + |24, 25, 26| + | ]; -// data3: [||Int||] +// data3 is of type [||Int||]. -for (plane in data) { - // plane: [|Int|] - for (row in plane) { - // row: [Int] +for plane in data { + // plane is of type [|Int|] + for row in plane { + // row is of type [Int] Message($"{row}"); } @@ -392,9 +393,10 @@ namespace Microsoft.Quantum.Arrays { let offset = NDArrayOffset(array); let shape = NDArrayShape(array); - // Now use AsNDArray2 to reconstruct, but with shape and strides + // Now use the internal AsNDArrayUnsafe function + // to reconstruct, but with shape and strides // reversed. - return AsNDArray2(data, strides, offset, shape); + return AsNDArrayUnsafe<'T, [|'T|]>(data, strides, offset, shape); } function ConstantArray2<'T>(shape : (Int, Int), element : 'T) : [|'T|] { @@ -404,7 +406,7 @@ namespace Microsoft.Quantum.Arrays { // Here, we set a stride of zero to store everything as a single // element. Using the copy-and-update operator will require actually // allocating the whole array, but we can start off by "cheating." - return AsNDArray2([element], [0, 0], 0, shape); + return AsNDArrayUnsafe<'T, [|'T|]>([element], [0, 0], 0, shape); } function Shape2<'T>(array : [|'T|]) : (Int, Int) { @@ -425,7 +427,7 @@ namespace Microsoft.Quantum.Arrays { set data += [left[(idxRow, idxCol)] * right[(idxRow, idxCol)]]; } } - return AsNDArray2(data, [1, nRows], 0, shape); + return AsNDArrayUnsafe(data, [1, nRows], 0, shape); } } ``` From 5484e48562042bd92985e43cd474768bb4cc96f3 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 2 Mar 2021 14:17:57 -0800 Subject: [PATCH 16/33] Addressed most feedback. --- Approved/3-multidimensional-arrays.md | 198 +++++++++++++++----------- 1 file changed, 114 insertions(+), 84 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 4ca598d..4d6939f 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -157,20 +157,18 @@ When used in `for` loops, multidimensional arrays iterate "on the left," yieldin Finally, to support multidimensional arrays, this proposal also suggests extending the `Microsoft.Quantum.Arrays` namespace with the following functions that can be used to implement libraries for working with multidimensional arrays: -- `internal function AsNDArray2<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [|'TElement|]` -- `internal function AsNDArray3<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [||'TElement||]` -- `internal function AsNDArray4<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [|||'TElement|||]` -- `internal function AsNDArray5<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [||||'TElement||||]` -- `internal function AsNDArray6<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [|||||'TElement|||||]` -- `internal function AsNDArray7<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [||||||'TElement||||||]` -- `internal function AsNDArray8<'TElement>(data : ['TElement], strides : [Int], offset : Int, shape : [Int]) : [|||||||'TElement|||||||]` -- `internal function NDArrayData<'TElement, 'TArray>(data : 'TArray) : ['TElement]` -- `internal function NDArrayStrides<'TArray>(data : 'TArray) : [Int]` -- `internal function NDArrayShape<'TArray>(data : 'TArray) : [Int]` -- `internal function NDArrayOffset<'TArray>(data : 'TArray) : Int` +- `internal function AsNDArrayUnsafe<'TElement, 'TArray>(data : ['TElement], strides : [Int], offset : Int, size : [Int]) : 'TArray` +- `internal function NDArrayDataUnsafe<'TElement, 'TArray>(data : 'TArray) : ['TElement]` +- `internal function NDArrayStridesUnsafe<'TArray>(data : 'TArray) : [Int]` +- `internal function NDArraySizeUnsafe<'TArray>(data : 'TArray) : [Int]` +- `internal function NDArrayOffsetUnsafe<'TArray>(data : 'TArray) : Int` Each of these five functions would be `body intrinsic;`, and together form the contract between the runtime and the core Q# libraries needed to support this proposed feature (see Example 7, below). By necessity, these functions are "unsafe," in that direct access to these functions would allow violating invariants of multidimensional arrays or bypass the type system to expose runtime failures, necessitating the `internal` modifier. +In user code, multidimensional arrays can be created and manipulated using Q# standard library functions and operations built up using the above internal functions, as described in example 7 below. +For instance, the "diagonal" elements of a 2-D array can be extracted into a 1-D array using `Diagonal2<'T>(array : [|'T|]) : ['T]`, as shown in example 8, below. +Please see https://github.com/microsoft/QuantumLibraries/issues for more details about library functions for working with multidimensional arrays. + #### Examples Example 1: @@ -178,30 +176,30 @@ Declaring and indexing into variables of type `[|Double|]` and `[||Int||]` using ```qsharp let z = [ - // Inside [] delimiters, [| |] delimiters refer not to array expressions, but + // Inside [] delimiters, | | delimiters refer not to array expressions, but // to "rows" of the two-dimensional array literal. - [| 1.0, 0.0 |], - [| 0.0, -1.0 |] + | 1.0, 0.0 |, + | 0.0, -1.0 | ]; Message($"{z[0, 1]}"); // ← 0.0 -// It is a compile-time error for the "rows" denoted by [| ... |] to be uneven: +// It is a compile-time error for the "rows" denoted by | ... | to be uneven: let incorrect = [ - [| 1.0, 2.0 |], - [| 10.0, 20.0, 30.0 |] // ← error, since this would declare a "ragged" array + | 1.0, 2.0 |, + | 10.0, 20.0, 30.0 | // ← error, since this would declare a "ragged" array ]; let data = [ - // We can nest two levels of [| ... |] to get three-dimensional arrays. - [| - [|0, 1|], - [|2, 3|] - |], - - [| - [|4, 5|], - [|6, 7|] - |] + // We can nest two levels of | ... | to get three-dimensional arrays. + | + |0, 1|, + |2, 3| + |, + + | + |4, 5|, + |6, 7| + | ]; Message($"{data[0, 1, 0]}"); // ← 6 ``` @@ -213,16 +211,16 @@ Mixing 1D and multidimensional arrays. // Here, we declare the first two levels of indexing as being rectangular, // resulting in a three-dimensional array. let data = [ - [| + | // After rows, [] denote arrays as elements again. - [| [0], [1, 2] |], - [| [3, 4, 5], [6, 7, 8, 9] |] - |], - - [| - [| [10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20] |], - [| [21, 22, 23, 24, 25, 26, 27], [28, 29, 30, 31, 32, 33, 34, 35] |] - |] + | [0], [1, 2] |, + | [3, 4, 5], [6, 7, 8, 9] | + |, + + | + | [10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20] |, + | [21, 22, 23, 24, 25, 26, 27], [28, 29, 30, 31, 32, 33, 34, 35] | + | ]; // data: [||[Int]||] (that is, a three-dimensional array of arrays of integers) // Get the zero-th "plane," first "row", zeroth "column," and third element. @@ -234,20 +232,20 @@ Using expressions as subarrays of multidimensional arrays results in a compile-t ```qsharp let a = [2, 3]; -// The following is a compile-time error, since `a` is not a 1D array literal -// of length 2. +// The following is a compile-time error, as `|0, 1|` is not an expression +// independent of its surrounding `[]` brackets. let data = [ - [| 0, 1 |], + | 0, 1 |, a ]; // Using a new library function that can `fail` at runtime works, however. -let data = Concatenated(0, // concatenate along the 0th (row) axis - [ [| 0, 1 |] ], +let data = Concatenated2(0, // concatenate along the 0th (row) axis + [ | 0, 1 | ], a ); // data: [|Int|] = [ -// [|0, 1|], -// [|2, 3|] +// |0, 1|, +// |2, 3| // ]; ``` @@ -267,15 +265,15 @@ Slicing multidimensional arrays by ranges. ```qsharp let data = [ - [|0, 1, 2|], - [|3, 4, 5|], - [|6, 7, 8|] + |0, 1, 2|, + |3, 4, 5|, + |6, 7, 8| ]; // Slicing an index by a Range does not reduce the dimensionality // of the resulting array. let corners = data[0..2..2, 0..2..2]; -// corners: [|Int|] = [[|0, 2|], [|6, 8|]] +// corners = [|0, 2|, |6, 8|] and is of type [|Int|] // Subscripting by an Int reduces the dimensionality of the resulting array; // here, since our index has one Int, the dimensionality reduces from @@ -287,23 +285,23 @@ let firstColumn = data[..., 0]; // firstColumn = [0, 3, 6] let data3 = [ - [| + | [|0, 1, 2|], [|3, 4, 5|], [|6, 7, 8|] - |], - - [| - [|9, 10, 11|], - [|12, 13, 14|], - [|15, 16, 17|] - |], - - [| - [|18, 19, 20|], - [|21, 22, 23|], - [|24, 25, 26|] - |] + |, + + | + |9, 10, 11|, + |12, 13, 14|, + |15, 16, 17| + |, + + | + |18, 19, 20|, + |21, 22, 23|, + |24, 25, 26| + | ]; let corners3 = data3[0..2..2, 0..2..2, 0..2..2]; // corners3 is of type [||Int||] @@ -317,7 +315,7 @@ Example 6: Using multidimensional slices in copy-and-update expressions. ```qsharp -let zeros =[|0, size = (3, 3)|]); +let zeros =[|0, size = (3, 3)|]; let withCorners = zeros w/ (0..2..2, 0..2..2) <- [|1, 2|, |3, 4|]; // withCorners = [|1, 0, 2|, |0, 0, 0|, |3, 0, 4|] and is of type [|Int|]. @@ -389,49 +387,81 @@ namespace Microsoft.Quantum.Arrays { // Start by deconstructing the input using the internal intrinsic // functions from this proposal; see Implementation below. let data = NDArrayData<'T, [|'T|]>(array); - let strides = NDArrayStrides(array); - let offset = NDArrayOffset(array); - let shape = NDArrayShape(array); + let strides = NDArrayStridesUnsafe(array); + let offset = NDArrayOffsetUnsafe(array); + let size = NDArraySizeUnsafe(array); // Now use the internal AsNDArrayUnsafe function - // to reconstruct, but with shape and strides + // to reconstruct, but with size and strides // reversed. - return AsNDArrayUnsafe<'T, [|'T|]>(data, strides, offset, shape); + return AsNDArrayUnsafe<'T, [|'T|]>(data, strides[...-1...], offset, size[...-1...]); } - function ConstantArray2<'T>(shape : (Int, Int), element : 'T) : [|'T|] { - Fact(Fst(shape) >= 0, "First axis had negative length."); - Fact(Snd(shape) >= 0, "Second axis had negative length."); + function ConstantArray2<'T>(size : (Int, Int), element : 'T) : [|'T|] { + Fact(Fst(size) >= 0, "First axis had negative length."); + Fact(Snd(size) >= 0, "Second axis had negative length."); // Here, we set a stride of zero to store everything as a single // element. Using the copy-and-update operator will require actually // allocating the whole array, but we can start off by "cheating." - return AsNDArrayUnsafe<'T, [|'T|]>([element], [0, 0], 0, shape); + return AsNDArrayUnsafe<'T, [|'T|]>([element], [0, 0], 0, size); } - function Shape2<'T>(array : [|'T|]) : (Int, Int) { - let shape = NDArrayShape(array); - return (shape[0], shape[1]); + function Size2<'T>(array : [|'T|]) : (Int, Int) { + let size = NDArraySizeUnsafe(array); + return (size[0], size[1]); } function TimesD2(left : [|Double|], right : [|Double|]) : [|Double|] { // For simplicity, we presume that left and right already match each - // other's shape exactly. In an actual library implementation, we would + // other's size exactly. In an actual library implementation, we would // want to generalize this to allow arbitrary binary operators, and to // handle broadcasting between the two inputs. mutable data = []; - let (nRows, nCols) = Shape2(left); + let (nRows, nCols) = Size2(left); for idxCol in 0..nCols - 1 { for idxRow in 0..nRows - 1 { set data += [left[(idxRow, idxCol)] * right[(idxRow, idxCol)]]; } } - return AsNDArrayUnsafe(data, [1, nRows], 0, shape); + return AsNDArrayUnsafe(data, [1, nRows], 0, [nRows, nCols]); } } ``` +Example 8: +Using array library functions to work with 2D arrays. + +```qsharp +let array = JaggedAsRectangular2([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10, 11, 12] +]); +// array = [ |1, 2, 3|, |4, 5, 6|, |7, 8, 9|, |10, 11, 12| ] and has type [|Int|]. +let size = Size2(array); +// size = (4, 3); +let diag = Diagonal2(array); // diag = [1, 5, 9]; +let sum = Folded(PlusI, 0, _); +let rowSums = FoldedAlongAxis2(sum, 0, array); +// rowSums = [6, 15, 24, 33]; +let colSums = FoldedAlongAxis2(sum, 1, array); +// colSums = [22, 26, 30]; +let corners = Subarray2([(0, 0), (0, 2), (3, 0), (3, 2)], array); +// corners = [1, 3, 10, 12]; +let x = [ + |Complex(0.0, 0.0), Complex(1.0, 0.0)|, + |Complex(1.0, 0.0), Complex(0.0, 0.0)| +]; +let y = [ + |Complex(0.0, 0.0), Complex(0.0, -1.0)|, + |Complex(0.0, 1.0), Complex(0.0, 0.0)| +]; +let z = Dot22C(x, y); +``` + ## Implementation **NB: Code samples in this section are intended as pseudocode only, and may not work directly as written.** @@ -444,7 +474,7 @@ In particular, each of these libraries represents multidimensional arrays by a d newtype MultidimensionalArray<'T> = ( Data: ['T], Offset: Int, - Shape: [Int], + Size: [Int], Strides: [Int] ); ``` @@ -458,13 +488,13 @@ array::Data[array::Offset + i * array::Strides[0] + j * array::Strides[1] + k * ``` This allows for many common array manipulations to be performed without copying. -For example, a function implementing a matrix transpose need not modify `::Data`, but can reverse `::Strides` and `::Shape`: +For example, a function implementing a matrix transpose need not modify `::Data`, but can reverse `::Strides` and `::Size`: ```qsharp function Transposed2<'T>(array : [|'T|]) : [|'T|] { return array w/ Strides <- array::Strides[...-1...] - w/ Shape <- array::Shape[...-1...]; + w/ Size <- array::Size[...-1...]; } ``` @@ -475,14 +505,14 @@ array[..., 0..2...] // implemented by: array w/ Strides <- [array::Strides[0], 2 * array::Strides[1]] - w/ Shape <- [array::Shape[0], array::Shape[1] / 2] + w/ Size <- [array::Size[0], array::Size[1] / 2] ``` Reversing an axis can be implemented by using negative strides and modifications to `::Offset`. By implementing multidimensional arrays in this way, we can reuse the existing infrastructure for immutable single-dimensional arrays. Moreover, in many cases, multiple distinct multidimensional arrays can share the same `::Data` item without requiring a copy. -For example, in the variable binding `let view = array[0..2..., 0..2...];`, `view::Data` and `array::Data` can be the same single-dimensional array, as the difference between `view` and `data` can be expressed by only modifying `::Strides` and `::Shape`. +For example, in the variable binding `let view = array[0..2..., 0..2...];`, `view::Data` and `array::Data` can be the same single-dimensional array, as the difference between `view` and `data` can be expressed by only modifying `::Strides` and `::Size`. The cases where copies may still be required are when reshaping from more indices to less, when using `w/` to update slices, or if a particular intrinsic function or operation is implemented in simulation by interoperating with native libraries such as BLAS and LAPACK. ### Timeline @@ -508,7 +538,7 @@ In particular: - Like existing arrays, multidimensional arrays are immutable values. - Mutability can be handled using `set ... w/= ...;` statements in the same fashion as existing 1D arrays. - Multidimensional arrays can be used as collections in loops. -- There are no subtyping relationships between array types. In particular, `[|'T|]` is not a subtype of `'T[][]` but a distinct type in its own right; nor is `('T => Unit is Adj)[,]` a subtype of `('T => Unit)[,]`. +- There are no subtyping relationships between array types. In particular, `[|'T|]` is not a subtype of `'T[][]` but a distinct type in its own right; nor is `[|('T => Unit is Adj)|]` a subtype of `[|('T => Unit)|]`. Similarly, `[|'T|]` is neither a subtype nor supertype of `[['T]]`. ### Impact on Existing Mechanisms @@ -698,4 +728,4 @@ While providing syntactic sugar for copy-and-update operations on jagged arrays ## Raised Concerns -## Response +## Response From 288d6c777aa438113654acdbaf4cda400232cd4c Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 2 Mar 2021 14:22:19 -0800 Subject: [PATCH 17/33] Deleted outdated note. --- Approved/3-multidimensional-arrays.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 4d6939f..83a586f 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -544,8 +544,6 @@ In particular: This proposal would not modify or invalidate existing functionality (e.g.: `Int[][]` will continue to be a valid type in Q#), and thus is not a breaking change. -If, in a future proposal, we were to unify multidimensional array with existing functionality using features outlined in _Anticipated Interactions with Future Modifications_, a breaking change would likely be required at that point in time. - ### Anticipated Interactions with Future Modifications #### Array comprehensions From f8ad9c8318e569be11e943583f1fa5cf1e9234f6 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 2 Mar 2021 18:20:06 -0800 Subject: [PATCH 18/33] Addressing feedback. --- Approved/3-multidimensional-arrays.md | 30 +++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 83a586f..f2fb954 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -143,14 +143,16 @@ For example, `[| element, size=(2, 3) |]` is equivalent to `[|element, element, Elements of a value of type `[|'T|]` can be retrieved using the subscript operator `[]` with a value of type `(Int, Int)` as the index, as in `data[(0, 1)]`. For brevity, the parentheses marking the tuple can be dropped in this case, such that `data[(0, 1)]` and `data[0, 1]` are completely equivalent. Similarly, elements of a value of type `[||'T||]` can be retrieved by subscripting with indices of type `(Int, Int, Int)`. -Multidimensional indices can also be used with the copy-and-update operator (`w/`) to create a new array that replaces one or more specific element(s) of a multidimensional array, as shown in Example 4 below. -In the case of `w/` operators, the `()` around index tuples cannot be dropped. As with one-dimensional arrays, multidimensional arrays can also be subscripted by ranges. Each axis of a multidimensional arrays can be sliced by _either_ a value of type `Range` or a value of type `Int`; for example, `(Int, Int)`, `(Range, Int)`, `(Int, Range)`, and `(Range, Range)` are valid subscripts for a value of type `[|'T|]`. As shown in Example 5 below, for each `Int` in an index tuple, the dimensionality (aka rank) of the array is reduced by one. That is, indexing a `[|'T|]` by `(Range, Range)` returns a rank-2 array (`[|'T|]`), while indexing by `(Int, Range)` or `(Range, Int)` returns an ordinary rank-1 array (`['T]`). + Just as with indices like `(Int, Int)` and `(Int, Int, Int)`, subscripts that return slices can also be used in copy-and-update expressions, as shown in Example 6. +Multidimensional indices can also be used with the copy-and-update operator (`w/`) to create a new array that replaces one or more specific element(s) of a multidimensional array, as shown in Example 4 below. +In the case of `w/` operators, the `()` around index tuples cannot be dropped. + When using values of type `Range` to index one or more axes in a multidimensional array, `...` is shorthand for the value `0..1..(n - 1)` where `n` is the length of the axis being indexed. When used in `for` loops, multidimensional arrays iterate "on the left," yielding loop variables of one rank lower than the array being looped over, as shown in Example 7, below. @@ -163,7 +165,7 @@ Finally, to support multidimensional arrays, this proposal also suggests extendi - `internal function NDArraySizeUnsafe<'TArray>(data : 'TArray) : [Int]` - `internal function NDArrayOffsetUnsafe<'TArray>(data : 'TArray) : Int` -Each of these five functions would be `body intrinsic;`, and together form the contract between the runtime and the core Q# libraries needed to support this proposed feature (see Example 7, below). By necessity, these functions are "unsafe," in that direct access to these functions would allow violating invariants of multidimensional arrays or bypass the type system to expose runtime failures, necessitating the `internal` modifier. +Each of these five functions would be `body intrinsic;`, and together form the contract between the runtime and the core Q# libraries needed to support this proposed feature (see Example 7, below). By necessity, these functions are "unsafe," in that direct access to these functions would allow violating invariants of multidimensional arrays or bypass the type system to expose runtime failures, necessitating the `internal` modifier. In particular, these functions may be given access to elements that do not exist insofar as Q# user code is concerned (i.e.: are not accessible given the current strides, shapes, and offsets); these elements may have reference counts of zero, such that referring to these elements is undefined behavior. It is the responsibility of the Q# foundation, core, and standard libraries to ensure that undefined behavior is not exposed to user code. In user code, multidimensional arrays can be created and manipulated using Q# standard library functions and operations built up using the above internal functions, as described in example 7 below. For instance, the "diagonal" elements of a 2-D array can be extracted into a 1-D array using `Diagonal2<'T>(array : [|'T|]) : ['T]`, as shown in example 8, below. @@ -286,9 +288,9 @@ let firstColumn = data[..., 0]; let data3 = [ | - [|0, 1, 2|], - [|3, 4, 5|], - [|6, 7, 8|] + |0, 1, 2|, + |3, 4, 5|, + |6, 7, 8| |, | @@ -374,6 +376,22 @@ for plane in data { // [18, 19, 20] // [21, 22, 23] // [24, 25, 26] + +// Iteration "on the left" is equivalent to iterating over the leftmost index +// of a multidimensional array: +let data2 = [ + |0, 1|, + |2, 3 +]; +// In particular, the following loops are identical up to that the idxRow +// variable is not defined in the second loop. +for idxRow in 0..Fst(Size2(data2)) - 1 { + let row = data2[idxRow, ...]; + // ... +} +for row in array { + // ... +} ``` Example 7: From f901e2604dcff65ce34f5435e8281125657e2aa4 Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:15:35 -0800 Subject: [PATCH 19/33] Update Approved/3-multidimensional-arrays.md --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index f2fb954..2ebc450 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -125,7 +125,7 @@ let e01 = ElementaryMatrix((2, 2), (0, 1), 0.0, 1.0); ### Proposed Modification -Building on the utility of 1-D array notation, this suggestion proposes modifying Q# to include new multidimensional array types `[|'T`|], `[||'T||]`, and so forth. Like values of type `'T[]`, these new multidimensional would also be immutable, and could be manipulated by using the subscript (`[]`) and copy-and-update (`w/`) operators. +Building on the utility of 1-D array notation, this suggestion proposes modifying Q# to include new multidimensional array types `[|'T|]`, `[||'T||]`, and so forth. Like values of type `'T[]`, these new multidimensional would also be immutable, and could be manipulated by using the subscript (`[]`) and copy-and-update (`w/`) operators. For any type `'T`, this proposal introduces a new two-dimensional array type `[|'T|]`, a three-dimensional array type `[||'T||]`, and so forth. For consistency with these new types, this proposal also introduces `['T]` as alternative notation for `'T[]`; we will use new notation for `'T[]` in the remainder of the proposal. From e87d87a10fdfe4479b3a393d036bfd701f6ba785 Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:17:01 -0800 Subject: [PATCH 20/33] Update Approved/3-multidimensional-arrays.md --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 2ebc450..6ad4aab 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -131,7 +131,7 @@ For any type `'T`, this proposal introduces a new two-dimensional array type `[| For consistency with these new types, this proposal also introduces `['T]` as alternative notation for `'T[]`; we will use new notation for `'T[]` in the remainder of the proposal. New values of type `[|'T|]` can be written as literals using `[| ... |]` delimiters instead of `[]`, while literals of type `[||'T||]` can be written using `[|| ... ||]` delimiters (see example 1 below). -The `|` and `|` delimeters occuring within square brackets can be thought of as denoting a rectangular grid, and as constraining one level of indexing to be rectangular. +The `|` and `|` delimiters occurring within square brackets can be thought of as denoting a rectangular grid, and as constraining one level of indexing to be rectangular. Similarly, new values of type `[|'T|]` can also be obtained by using the library functions in https://github.com/microsoft/QuantumLibraries/issues/408; e.g., `JaggedAsRectangular2([[2, 3], [4, 5]])` is equivalent to the literal `[|2, 3|, |4, 5|]`. Within multidimensional array literals, it is a _compile-time_ error to declare jagged subarrays, such as in Example 1, below. From add6a6f514e76403c93fe89a4e3da353c324153d Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:24:57 -0800 Subject: [PATCH 21/33] Update Approved/3-multidimensional-arrays.md --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 6ad4aab..fa710d9 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -153,7 +153,7 @@ Just as with indices like `(Int, Int)` and `(Int, Int, Int)`, subscripts that re Multidimensional indices can also be used with the copy-and-update operator (`w/`) to create a new array that replaces one or more specific element(s) of a multidimensional array, as shown in Example 4 below. In the case of `w/` operators, the `()` around index tuples cannot be dropped. -When using values of type `Range` to index one or more axes in a multidimensional array, `...` is shorthand for the value `0..1..(n - 1)` where `n` is the length of the axis being indexed. +When using values of type `Range` to index one or more axes in a multidimensional array the contextual `Range` expression `...`, e.g., is shorthand for the value `0..1..(n - 1)` where `n` is the length of the axis being indexed. The same [contextual expressions](https://github.com/microsoft/qsharp-language/blob/main/Specifications/Language/3_Expressions/ContextualExpressions.md#contextual-and-omitted-expressions) as for the one-dimensional case are valid for each dimension in the array. When used in `for` loops, multidimensional arrays iterate "on the left," yielding loop variables of one rank lower than the array being looped over, as shown in Example 7, below. From 130d8a64d84ffdc4d42af4c147897f081a42f45e Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:26:20 -0800 Subject: [PATCH 22/33] Apply suggestions from code review --- Approved/3-multidimensional-arrays.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index fa710d9..5dff70b 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -4,7 +4,7 @@ author: Chris Granade date: 2021-03-01 --- -# QEP 3: Multi-dimensional arrays +# QEP 3: Multidimensional arrays ## Proposal @@ -135,7 +135,7 @@ The `|` and `|` delimiters occurring within square brackets can be thought of as Similarly, new values of type `[|'T|]` can also be obtained by using the library functions in https://github.com/microsoft/QuantumLibraries/issues/408; e.g., `JaggedAsRectangular2([[2, 3], [4, 5]])` is equivalent to the literal `[|2, 3|, |4, 5|]`. Within multidimensional array literals, it is a _compile-time_ error to declare jagged subarrays, such as in Example 1, below. -It is similarly a compile-time error to use a non-literal array expression for part of a multi-dimensional array literal, as is shown in Example 3, below. +It is similarly a compile-time error to use a non-literal array expression for part of a multidimensional array literal, as is shown in Example 3, below. Multidimensional arrays can also be created using extensions of the functionality in QEP 2. For example, `[| element, size=(2, 3) |]` is equivalent to `[|element, element, element|, |element, element, element|]`. From c0d7eac1d99e69eb1eedad281f5f3b9d876fb0a0 Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:28:59 -0800 Subject: [PATCH 23/33] Apply suggestions from code review --- Approved/3-multidimensional-arrays.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 5dff70b..da9431f 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -381,7 +381,7 @@ for plane in data { // of a multidimensional array: let data2 = [ |0, 1|, - |2, 3 + |2, 3| ]; // In particular, the following loops are identical up to that the idxRow // variable is not defined in the second loop. @@ -389,7 +389,7 @@ for idxRow in 0..Fst(Size2(data2)) - 1 { let row = data2[idxRow, ...]; // ... } -for row in array { +for row in data2 { // ... } ``` From 2b0726170e27510234bc3ae115e97e0007f31f99 Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:31:37 -0800 Subject: [PATCH 24/33] Update Approved/3-multidimensional-arrays.md --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index da9431f..9a5a796 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -165,7 +165,7 @@ Finally, to support multidimensional arrays, this proposal also suggests extendi - `internal function NDArraySizeUnsafe<'TArray>(data : 'TArray) : [Int]` - `internal function NDArrayOffsetUnsafe<'TArray>(data : 'TArray) : Int` -Each of these five functions would be `body intrinsic;`, and together form the contract between the runtime and the core Q# libraries needed to support this proposed feature (see Example 7, below). By necessity, these functions are "unsafe," in that direct access to these functions would allow violating invariants of multidimensional arrays or bypass the type system to expose runtime failures, necessitating the `internal` modifier. In particular, these functions may be given access to elements that do not exist insofar as Q# user code is concerned (i.e.: are not accessible given the current strides, shapes, and offsets); these elements may have reference counts of zero, such that referring to these elements is undefined behavior. It is the responsibility of the Q# foundation, core, and standard libraries to ensure that undefined behavior is not exposed to user code. +Each of these five functions would be `body intrinsic;`, and together form the contract between the runtime and the core Q# libraries needed to support this proposed feature (see Example 7, below). By necessity, these functions are "unsafe," in that direct access to these functions would allow violating invariants of multidimensional arrays or bypass the type system to expose runtime failures, necessitating the `internal` modifier. In particular, these functions may be given access to elements that do not exist insofar as Q# user code is concerned (i.e.: are not accessible given the current strides, shapes, and offsets); these elements may have reference counts of zero, such that referring to these elements results in undefined behavior. It is the responsibility of the Q# foundation, core, and standard libraries to ensure that undefined behavior is not exposed to user code. In user code, multidimensional arrays can be created and manipulated using Q# standard library functions and operations built up using the above internal functions, as described in example 7 below. For instance, the "diagonal" elements of a 2-D array can be extracted into a 1-D array using `Diagonal2<'T>(array : [|'T|]) : ['T]`, as shown in example 8, below. From a99fe204dbcdbe636b394ce05085455719d192d0 Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:46:51 -0800 Subject: [PATCH 25/33] Apply suggestions from code review --- Approved/3-multidimensional-arrays.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 9a5a796..1ac70f3 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -203,7 +203,7 @@ let data = [ |6, 7| | ]; -Message($"{data[0, 1, 0]}"); // ← 6 +Message($"{data[1, 1, 0]}"); // ← 6 ``` Example 2: @@ -224,8 +224,8 @@ let data = [ | [21, 22, 23, 24, 25, 26, 27], [28, 29, 30, 31, 32, 33, 34, 35] | | ]; -// data: [||[Int]||] (that is, a three-dimensional array of arrays of integers) -// Get the zero-th "plane," first "row", zeroth "column," and third element. +// data is of type [||[Int]||] (that is, a three-dimensional array of arrays of integers) +// In the zero-th "plane", first "row", zero-th "column", get the element with index 2. Message($"{data[0, 1, 0][2]}"); // ← 5 ``` @@ -252,7 +252,7 @@ let data = Concatenated2(0, // concatenate along the 0th (row) axis ``` Example 4: -Using the copy-and-replace operator to manipulate multidimensional arrays. +Using the copy-and-update operator to manipulate multidimensional arrays. ```qsharp function ElementaryMatrix( From 336ec7588bdf464cfaefe29107b584ffe91bd5cd Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:51:07 -0800 Subject: [PATCH 26/33] Apply suggestions from code review --- Approved/3-multidimensional-arrays.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 1ac70f3..2985c09 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -258,12 +258,12 @@ Using the copy-and-update operator to manipulate multidimensional arrays. function ElementaryMatrix( (nRows : Int, nCols : Int), (idxRow : Int, idxCol : Int) ) : [|Double|] { - return ConstantArray2((nRows, nCols), 0.0) w/ (idxRow, idxCol) <- 1.0; + return [| 0.0, size = (nRows, nCols) |] w/ (idxRow, idxCol) <- 1.0; } ``` Example 5: -Slicing multidimensional arrays by ranges. +Slicing multidimensional arrays. ```qsharp let data = [ @@ -272,8 +272,7 @@ let data = [ |6, 7, 8| ]; -// Slicing an index by a Range does not reduce the dimensionality -// of the resulting array. +// Slicing does not reduce the dimensionality of the resulting array. let corners = data[0..2..2, 0..2..2]; // corners = [|0, 2|, |6, 8|] and is of type [|Int|] From cc41db20803ef3de3dd6f3ba3ed4542fa7114041 Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 20:03:45 -0800 Subject: [PATCH 27/33] Apply suggestions from code review --- Approved/3-multidimensional-arrays.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 2985c09..e61e377 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -393,7 +393,7 @@ for row in data2 { } ``` -Example 7: +Example 8: Implementing array library functions using internal functions. ```qsharp @@ -447,7 +447,7 @@ namespace Microsoft.Quantum.Arrays { } ``` -Example 8: +Example 9: Using array library functions to work with 2D arrays. ```qsharp From 2dd828419a303539a0a4aabb439116c4388ae456 Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 20:14:21 -0800 Subject: [PATCH 28/33] Update Approved/3-multidimensional-arrays.md --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index e61e377..f90922f 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -530,7 +530,7 @@ Reversing an axis can be implemented by using negative strides and modifications By implementing multidimensional arrays in this way, we can reuse the existing infrastructure for immutable single-dimensional arrays. Moreover, in many cases, multiple distinct multidimensional arrays can share the same `::Data` item without requiring a copy. For example, in the variable binding `let view = array[0..2..., 0..2...];`, `view::Data` and `array::Data` can be the same single-dimensional array, as the difference between `view` and `data` can be expressed by only modifying `::Strides` and `::Size`. -The cases where copies may still be required are when reshaping from more indices to less, when using `w/` to update slices, or if a particular intrinsic function or operation is implemented in simulation by interoperating with native libraries such as BLAS and LAPACK. +The cases where copies may still be required are when constructing new arrays using a copy-and-update expression, or if a particular intrinsic function or operation is implemented in simulation by interoperating with native libraries such as BLAS and LAPACK, or when truncating the array. ### Timeline From a7583cbfc3e262969ac89b2f02f1377e3f793aa3 Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 20:18:28 -0800 Subject: [PATCH 29/33] Update Approved/3-multidimensional-arrays.md --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index f90922f..4cdd5b5 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -611,7 +611,7 @@ function Forced<'T>(maybe : Maybe<'T>) : 'T { #### Removing type suffixes with bounded polymorphism -Were the bounded polymorphism feature suggested at https://github.com/microsoft/qsharp-compiler/issues/557 to be adopted, the different "overloads" for the library functions suggested in this proposal could be consolidated into a smaller number of concepts that then get implemented by each of `'T[,]`, `'T[,,]`, and so forth. +Were the bounded polymorphism feature suggested at https://github.com/microsoft/qsharp-compiler/issues/557 to be adopted, the different "overloads" for the library functions suggested in this proposal could be consolidated into a smaller number of concepts that then get implemented by each of `[|'T|]`, `[||'T||]`, and so forth. For example: From 16aa2122442a1435b89b00687365c81463711b5c Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Thu, 4 Mar 2021 20:22:00 -0800 Subject: [PATCH 30/33] Update Approved/3-multidimensional-arrays.md --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 4cdd5b5..e278147 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -611,7 +611,7 @@ function Forced<'T>(maybe : Maybe<'T>) : 'T { #### Removing type suffixes with bounded polymorphism -Were the bounded polymorphism feature suggested at https://github.com/microsoft/qsharp-compiler/issues/557 to be adopted, the different "overloads" for the library functions suggested in this proposal could be consolidated into a smaller number of concepts that then get implemented by each of `[|'T|]`, `[||'T||]`, and so forth. +Were the bounded polymorphism feature suggested at https://github.com/microsoft/qsharp-compiler/issues/557 to be adopted, the different "overloads" for the library functions could be consolidated into a smaller number of concepts that then get implemented by each of `[|'T|]`, `[||'T||]`, and so forth. For example: From a65863be398c3c0f0baf1f3c9904e35f6c660979 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Mon, 8 Mar 2021 09:41:57 -0800 Subject: [PATCH 31/33] Addressing feedback. --- Approved/3-multidimensional-arrays.md | 97 +++------------------------ 1 file changed, 8 insertions(+), 89 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index e278147..e3dedad 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -151,6 +151,7 @@ That is, indexing a `[|'T|]` by `(Range, Range)` returns a rank-2 array (`[|'T|] Just as with indices like `(Int, Int)` and `(Int, Int, Int)`, subscripts that return slices can also be used in copy-and-update expressions, as shown in Example 6. Multidimensional indices can also be used with the copy-and-update operator (`w/`) to create a new array that replaces one or more specific element(s) of a multidimensional array, as shown in Example 4 below. +The `w/` operator can also be used with slices as well as indices, in which case the right-hand side of the `<-` arrow must be a multidimensional array whose size matches the slice being updated in the new copy, as shown in Example 6 below. In the case of `w/` operators, the `()` around index tuples cannot be dropped. When using values of type `Range` to index one or more axes in a multidimensional array the contextual `Range` expression `...`, e.g., is shorthand for the value `0..1..(n - 1)` where `n` is the length of the axis being indexed. The same [contextual expressions](https://github.com/microsoft/qsharp-language/blob/main/Specifications/Language/3_Expressions/ContextualExpressions.md#contextual-and-omitted-expressions) as for the one-dimensional case are valid for each dimension in the array. @@ -240,10 +241,15 @@ let data = [ | 0, 1 |, a ]; -// Using a new library function that can `fail` at runtime works, however. +// Using new library functions that can `fail` at runtime, however, we can +// achieve the desired effect. +// In particular, JaggedAsRectangular2<'T>(jagged : 'T[][]) -> [|'T|] allows +// us to build a [|Int|] value from `a`, which we can then join to `data` using +// the Concatenated2<'T>(axis : Int, left : [|'T|], right : [|'T|]) +// -> [|'T|] function: let data = Concatenated2(0, // concatenate along the 0th (row) axis [ | 0, 1 | ], - a + JaggedAsRectangular2([a]) ); // data: [|Int|] = [ // |0, 1|, @@ -392,93 +398,6 @@ for row in data2 { // ... } ``` - -Example 8: -Implementing array library functions using internal functions. - -```qsharp -namespace Microsoft.Quantum.Arrays { - open Microsoft.Quantum.Diagnostics; - - function Transposed2<'T>(array : [|'T|]) : [|'T|] { - // Start by deconstructing the input using the internal intrinsic - // functions from this proposal; see Implementation below. - let data = NDArrayData<'T, [|'T|]>(array); - let strides = NDArrayStridesUnsafe(array); - let offset = NDArrayOffsetUnsafe(array); - let size = NDArraySizeUnsafe(array); - - // Now use the internal AsNDArrayUnsafe function - // to reconstruct, but with size and strides - // reversed. - return AsNDArrayUnsafe<'T, [|'T|]>(data, strides[...-1...], offset, size[...-1...]); - } - - function ConstantArray2<'T>(size : (Int, Int), element : 'T) : [|'T|] { - Fact(Fst(size) >= 0, "First axis had negative length."); - Fact(Snd(size) >= 0, "Second axis had negative length."); - - // Here, we set a stride of zero to store everything as a single - // element. Using the copy-and-update operator will require actually - // allocating the whole array, but we can start off by "cheating." - return AsNDArrayUnsafe<'T, [|'T|]>([element], [0, 0], 0, size); - } - - function Size2<'T>(array : [|'T|]) : (Int, Int) { - let size = NDArraySizeUnsafe(array); - return (size[0], size[1]); - } - - function TimesD2(left : [|Double|], right : [|Double|]) : [|Double|] { - // For simplicity, we presume that left and right already match each - // other's size exactly. In an actual library implementation, we would - // want to generalize this to allow arbitrary binary operators, and to - // handle broadcasting between the two inputs. - - mutable data = []; - let (nRows, nCols) = Size2(left); - for idxCol in 0..nCols - 1 { - for idxRow in 0..nRows - 1 { - set data += [left[(idxRow, idxCol)] * right[(idxRow, idxCol)]]; - } - } - return AsNDArrayUnsafe(data, [1, nRows], 0, [nRows, nCols]); - } -} -``` - -Example 9: -Using array library functions to work with 2D arrays. - -```qsharp -let array = JaggedAsRectangular2([ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - [10, 11, 12] -]); -// array = [ |1, 2, 3|, |4, 5, 6|, |7, 8, 9|, |10, 11, 12| ] and has type [|Int|]. -let size = Size2(array); -// size = (4, 3); -let diag = Diagonal2(array); // diag = [1, 5, 9]; -let sum = Folded(PlusI, 0, _); -let rowSums = FoldedAlongAxis2(sum, 0, array); -// rowSums = [6, 15, 24, 33]; -let colSums = FoldedAlongAxis2(sum, 1, array); -// colSums = [22, 26, 30]; -let corners = Subarray2([(0, 0), (0, 2), (3, 0), (3, 2)], array); -// corners = [1, 3, 10, 12]; -let x = [ - |Complex(0.0, 0.0), Complex(1.0, 0.0)|, - |Complex(1.0, 0.0), Complex(0.0, 0.0)| -]; -let y = [ - |Complex(0.0, 0.0), Complex(0.0, -1.0)|, - |Complex(0.0, 1.0), Complex(0.0, 0.0)| -]; -let z = Dot22C(x, y); -``` - ## Implementation **NB: Code samples in this section are intended as pseudocode only, and may not work directly as written.** From e0b58c83f760c8dcdb422dc07996261f14ca21fe Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Mon, 8 Mar 2021 09:50:27 -0800 Subject: [PATCH 32/33] One last point of feedback. --- Approved/3-multidimensional-arrays.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index e3dedad..78ba95c 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -642,15 +642,6 @@ let z = [ ]; ``` -These alternatives can possibly be combined with `[| |]`: - -```qsharp -let bellTableau = [ - true, true, false, false; // separate rows instead of nesting - false, false, true, true -]; -``` - ### Comparison to Alternatives #### Comparison to Syntactic Sugar for Jagged Arrays From 628a742287443a5ced4e331be7fa38c6cc1235ab Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Mon, 8 Mar 2021 10:58:37 -0800 Subject: [PATCH 33/33] Update Approved/3-multidimensional-arrays.md --- Approved/3-multidimensional-arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Approved/3-multidimensional-arrays.md b/Approved/3-multidimensional-arrays.md index 78ba95c..2a846cf 100644 --- a/Approved/3-multidimensional-arrays.md +++ b/Approved/3-multidimensional-arrays.md @@ -244,7 +244,7 @@ let data = [ // Using new library functions that can `fail` at runtime, however, we can // achieve the desired effect. // In particular, JaggedAsRectangular2<'T>(jagged : 'T[][]) -> [|'T|] allows -// us to build a [|Int|] value from `a`, which we can then join to `data` using +// us to build a [|Int|] value from `a`, which we can then join to get `data` using // the Concatenated2<'T>(axis : Int, left : [|'T|], right : [|'T|]) // -> [|'T|] function: let data = Concatenated2(0, // concatenate along the 0th (row) axis