QuantumLibraries
QuantumLibraries copied to clipboard
Windowed unitary application
Proposal title
Windowed Unitary application
Conceptual overview
Addition of a function to apply unitary operations windowed. Windowed operations occur in many quantum algorithms and operations. This proposal proposes a wrapper that will allow windowing of operations with dedicated target qubits to make code more semantic and to provide new users easier ways to implement such operations.
Current status
Currently there is support for creating windows on Arrays, i.e., diving them into chunks of a specific size but no support for windowing operations. So to window an operation requires manually splitting an array and applying the operation over it.
User feedback
N/A
Child issues
N/A
Proposal
New and modified functions, operations, and UDTs
In namespace Microsoft.Quantum.Arrays:
/// # Summary
/// Applies an operation windowing over input but with constant target qubits.
///
/// # Input
/// ## windowLen
/// The size of each window
/// ## op
/// An operation with three arguments, one index, controls, and target. When applied it will be supplied with a windowed component of the `qubits` parameter, the `target` qubits (constant) and an index of the window
/// ## qubits
/// The qubits the operation windows over
/// ## target
/// The target provided to each windowed operation
operation ApplyWindowed(windowLen : Int, op : (Int, Qubit[], Qubit[]) => Unit, qubits : Qubit[], target : Qubit[]) : Unit
Modifications to style guide
N/A
Impact of breaking changes
N/A
Examples
Current status
Currently the most efficient solution is to inline op in the following:
let windows = Windows(windowLen, qubits); // Create windows of non-target qubits
for (i, window) in Enumerated(windows) { op(argumentTransform(i), window, target);
}
Using proposed changes
Given this function is mainly a convivence wrapper it would merely involve the single function call.
Relationship to Q# language feature proposals
N/A
Alternatives considered
An alternative would be to include an explicit transformation of the index, however this can be encapsulated in op
/// # Summary
/// Applies an operation windowing over input but with constant target qubits.
///
/// # Input
/// ## windowLen
/// The size of each window
/// ## op
/// An operation with three arguments, one arbitrary, controls, and target. When applied it will be supplied with a windowed component of the `qubits` parameter, the `target` qubits (constant) and an arbitary parameter stemming from the `argumentTransform` mapping supplied.
/// ## argumentTransform
/// Transforms an integer into the arbitary argument for op. This function will be provide with the indices of the windows. This means the function need to work correctly for $[0, n[$, where $n$ is the number of windows.
/// ## qubits
/// The qubits the operation windows over
/// ## target
/// The targer provided to each windowed operation
/// # Type Parameters
/// ## 'T
/// Any type that the operation can use as parametrization
operation ApplyWindowed<'T>(windowLen : Int, op : ('T, Qubit[], Qubit[]) => Unit, argumentTransform : Int -> 'T, qubits : Qubit[], target : Qubit[]) : Unit
Open design questions and considerations
N/A
This looks like a useful new operation, thank you for the suggestion and your PR! From the standpoint of an API review, I'd make a few different suggestions on how to make it fit better within the standard libraries.
First, the target input isn't needed, since that can always be readily handled using partial application or lambas. In particular, looking at #603, the same value is passed to op each time it's called. I'd therefore suggest dropping target entirely as an input. That also makes it easier to work with types of targets other than Qubit[] (e.g.: singleton qubits, doubly nested arrays, little endian registers, etc.).
Second, you may want to window over something over than individual qubits in an array. Introducing a type parameter for that would be very helpful, since the body of ApplyWindowed never uses that the elements of qubits are of type Qubit.
Taking these together, you might get something like this as an implementation:
operation ApplyWindowed<'T>(windowLen : Int, op : (Int, 'T[]) => Unit, register : 'T[]) : Unit {
ApplyToEach(op, Enumerated(Windows(windowLen, register)));
}
Past that, I'd also suggest introducing A, C, and CA variants to allow using this operation from operations that need to be adjointable and/or controllable.
Thanks!
@cgranade Thank you for the suggestions, those indeed look more idiomatic - I'll update the PR
I'd like to combine @cgranade's suggestion to drop the target and make the array type generic, but I'd also like to include the argumentTransform into the signature, then having two generic arguments.
...but I'd also like to include the
argumentTransforminto the signature, then having two generic arguments.
I'm not sure I understand the reasoning there? That feels like it duplicates a lot of the functionality of Mapped and Enumerated; in particular, could always do something like let argumentTransform = ...; ApplyWindowed((idx, window) => ApplyFoo(argumentTransform(idx), window), register);.
What usecase are you looking at for including argumentTransform as an explicit input?
+1 on @cgranade on using lambda's instead of argumentTransform.
LGTM, I think just one last minor suggestion: ApplyToEachWindow would match ApplyToEach, ApplyToEachIndex, etc. a bit better than ApplyWindowed. Other than that, happy to approve with no further comment. Thanks!