Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

[WIP]: Proposal for allocatable types and generalization of initializers #41

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

bettinaheim
Copy link
Contributor

This proposal is work in progress. I am putting up a draft for early feedback and discussion.

| Built-in Initializer | Description | Allocated Type | Initializer Argument Type | Initializer Expression |
| --- | --- | --- | --- | --- |
| Qubit | allocates a single qubit | `Qubit` | `Unit` | `Qubit()` |
| Qubits | allocates a 1D array of qubits | `Qubit[]` | `Int` | `Qubits(4)` |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing arrays allocation syntax from Qubit[4] to Qubits(4) is significant enough to have a separate proposal. And looks like this can be done independently from the initializers proposal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, this does not modify array allocation (see microsoft/qsharp-compiler#589 for that proposal), but only the syntax for initializing registers of qubits.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me. Since we're discussing it here, I would like to better understand advantage of Qubits(4) over Qubit[4].

| `H` | `Qubit` | `Unit` | `init within H` | Yes |
| `Reset` | `Qubit` | `Int` | `init then Reset` | No |
| `ApplyToEachA(H, _)` | `Qubit[]` | `Int` | `init(3) within ApplyToEachA(H, _)` | Yes |
| `ApplyToEach(H, _)` | `Qubit[]` | `Int` | `init(3) then ApplyToEach(H, _)` | No |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I get it right that these two code snippets are the same?

using (a = Qubit[3]) {  ApplyToEach(H, a); }
using (b = init(3) then ApplyToEach(H, _)) {  }

If so, how much value do we get from the 'then' keyword?

Initializers built by elevating operations:
| Operation Valued Expression | Allocated Type | Initializer Argument Type | Initializer Expression | Uncompute upon Release |
| --- | --- | --- | --- | --- |
| `H` | `Qubit` | `Unit` | `init within H` | Yes |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, it's not obvious from this statement that we allocated qubits and not something else. I think that it is important to keep 'Qubit' in the syntax. May be:

using (q = Qubit()) { } // Exists today
using (qh = Qubit(H)) { } // One qubit in the H state, uncompute upon release.
using (r = Qubit[3]) { } // Exists today
using (rh = Qubit(H)[3]) { } // A register of 3 qubits in the H state, uncompute upon release.
using (r2 = [Qubit(H), Qubit(X)]) { } // Array of two qubits in different states.

A type is allocatable if it either is one of the built-in allocatable types, or if it contains one or more items which are allocatable. There are two built-in allocatable types: the type Qubit and the type Qubit[], or more generally n-dimensional rectangular Qubit arrays (as suggested [here](https://github.com/microsoft/qsharp-language/issues/39)).

### *Initializers for allocatable types*:
Initializers are special callables that return values of an allocatable type. Initializers are *neither* operations nor functions. They are their own distinct type with no subtyping relation to operation or function types; they cannot be used when a value of operation or function type is required. Initializers can only be used as part of [qubit allocation statements](https://github.com/microsoft/qsharp-language/blob/main/Specifications/Language/2_Statements/QuantumMemoryManagement.md#quantum-memory-management).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are initializers first-class values (i.e.: can I assign an initializer to a variable binding)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far I was not planning to support that - they could literally only be used within allocation statements. I don't see a reason to support it at this time. You can always pass an operation that is elevated though. Do you have a use case in mind where you would want to be able to pass an initializer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, wasn't trying to suggest that we need first-class initializers per se so much as to understand the scope of the proposal. Thanks for the clarification!

| --- | --- | --- | --- | --- |
| Qubit | allocates a single qubit | `Qubit` | `Unit` | `Qubit()` |
| Qubits | allocates a 1D array of qubits | `Qubit[]` | `Int` | `Qubits(4)` |
| Qubits | allocates a [2D array](https://github.com/microsoft/qsharp-language/issues/39) of qubits | `Qubit[,]` | `(Int, Int)` | `Qubits(2,4)` |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effectively, it seems this is a kind of overloading, in that Qubits can either have type Int ~> Qubit[] or (Int, Int) ~> Qubit[,]. Is that represented by a type parameter to Qubits?

Copy link
Contributor

@bamarsha bamarsha Nov 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overloading on tuple arity seems weird to me - it's compiler magic that can't be expressed in the language itself. I don't think it would not be possible to write a custom initializer that does this. A type parameter would allow any type, not just tuples of Ints.

| Built-in Initializer | Description | Allocated Type | Initializer Argument Type | Initializer Expression |
| --- | --- | --- | --- | --- |
| Qubit | allocates a single qubit | `Qubit` | `Unit` | `Qubit()` |
| Qubits | allocates a 1D array of qubits | `Qubit[]` | `Int` | `Qubits(4)` |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, this does not modify array allocation (see microsoft/qsharp-compiler#589 for that proposal), but only the syntax for initializing registers of qubits.

| --- | --- | --- | --- | --- |
| `H` | `Qubit` | `Unit` | `init within H` | Yes |
| `Reset` | `Qubit` | `Int` | `init then Reset` | No |
| `ApplyToEachA(H, _)` | `Qubit[]` | `Int` | `init(3) within ApplyToEachA(H, _)` | Yes |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's in scope, it may be helpful to use ApplyXorInPlace as an example, since that demonstrates how init/then can be used to really help out with preparing structured data. In particular, I think that builds well on the example at line 68.


### *Custom initializers for user defined types:*
Support for defining custom initializers for user defined types is intended to be added if and when custom constructors can be defined. Important considerations for what can and cannot be supported for such custom initializers, however, are part of this proposal to ensure that the proposed additions can be extended to cover that scenario as well.
In contrast to operations, initializers allocate and return qubits that remain live after their execution terminates. Such qubits are allocated via a dedicated statement, consisting of the keyword `initialize` followed by a symbol or symbol tuple, an equals sign `=`, and either an initializer or an initializer tuple.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having two keywords init and initialize that mean different things seems confusing to me. It's similar to the fun and function distinction in F#, which I also think is not very intuitive.

1. One is that an operation should be invoked on the allocated qubits after initialization, followed by further computations before the qubits are measured and released. In this case, it is not necessary or desirable that the operation performed upon initialization is un-computed upon release.
2. In the second case, the computations following the initialization lead to the qubits being in the same state as they were initialized. In this case, the adjoint of the operation used for initialization needs to be applied upon releasing the qubits.

The proposal is to syntactically distinguish the two cases. The table below contains examples for both cases. In the first case, the initializer consists of the keyword `init`, followed by the initializer argument tuple, the keyword `then` and an operation-valued expression. The initializer argument tuple may be omitted if the required argument is of type `Unit`. The second case follows the same pattern with the exception that instead of the keyword `then`, then keyword `within` should be used, and the operation-valued expression needs to support the `Adjoint` functor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initializer argument tuple may be omitted if the required argument is of type Unit.

We don't allow omitting () when calling functions or operations. What is the reason to support it here?

1. One is that an operation should be invoked on the allocated qubits after initialization, followed by further computations before the qubits are measured and released. In this case, it is not necessary or desirable that the operation performed upon initialization is un-computed upon release.
2. In the second case, the computations following the initialization lead to the qubits being in the same state as they were initialized. In this case, the adjoint of the operation used for initialization needs to be applied upon releasing the qubits.

The proposal is to syntactically distinguish the two cases. The table below contains examples for both cases. In the first case, the initializer consists of the keyword `init`, followed by the initializer argument tuple, the keyword `then` and an operation-valued expression. The initializer argument tuple may be omitted if the required argument is of type `Unit`. The second case follows the same pattern with the exception that instead of the keyword `then`, then keyword `within` should be used, and the operation-valued expression needs to support the `Adjoint` functor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The proposal is to syntactically distinguish the two cases. The table below contains examples for both cases. In the first case, the initializer consists of the keyword `init`, followed by the initializer argument tuple, the keyword `then` and an operation-valued expression. The initializer argument tuple may be omitted if the required argument is of type `Unit`. The second case follows the same pattern with the exception that instead of the keyword `then`, then keyword `within` should be used, and the operation-valued expression needs to support the `Adjoint` functor.
The proposal is to syntactically distinguish the two cases. The table below contains examples for both cases. In the first case, the initializer consists of the keyword `init`, followed by the initializer argument tuple, the keyword `then` and an operation-valued expression. The initializer argument tuple may be omitted if the required argument is of type `Unit`. The second case follows the same pattern with the exception that instead of the keyword `then`, the keyword `within` should be used, and the operation-valued expression needs to support the `Adjoint` functor.

A type is allocatable if it either is one of the built-in allocatable types, or if it contains one or more items which are allocatable. There are two built-in allocatable types: the type Qubit and the type Qubit[], or more generally n-dimensional rectangular Qubit arrays (as suggested [here](https://github.com/microsoft/qsharp-language/issues/39)).

### *Initializers for allocatable types*:
Initializers are special callables that return values of an allocatable type. Initializers are *neither* operations nor functions. They are their own distinct type with no subtyping relation to operation or function types; they cannot be used when a value of operation or function type is required. Initializers can only be used as part of [qubit allocation statements](https://github.com/microsoft/qsharp-language/blob/main/Specifications/Language/2_Statements/QuantumMemoryManagement.md#quantum-memory-management).
Copy link
Contributor

@bamarsha bamarsha Nov 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having three different types of callables in Q# is a lot, especially since initializer names overlap with both type names and type constructor names. The automatic conversion from e.g. LittleEndian(Qubit[]) to LittleEndian(Int) is also a little confusing - when I see LittleEndian(3), I have to remember that:

  1. It's in an initializer expression, so LittleEndian refers to the initializer, not the constructor, which takes a different argument type.
  2. The Int maps back to the length of a Qubit array, even though Qubit doesn't appear anywhere in the expression.

I think there is a lot of cognitive overhead needed to interpret this syntax, and I wonder if making the Qubit array explicit would be better, such as LittleEndian(Qubits(3)).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allocatable types and generalization of initializers
4 participants