From 8ff71150ed5644ba1e494c4cdf8bb4de98b08c66 Mon Sep 17 00:00:00 2001 From: Christopher Kang <42285636+christopherkang@users.noreply.github.com> Date: Fri, 22 Nov 2019 10:29:39 -0800 Subject: [PATCH] Add repeated operation (#153) * Drafted new apply ops * Fixed docs * Fixed minor bugs * Added Permutation function + helper Arrays + Claim functions * Added Adj + Ctl and set csproj back to generating docs * Added tests * Fixed test errors * Updated code from comments; moved PermuteQubits to CommonGates * Fixed minor bugs * Apply suggestions from code review Co-Authored-By: Chris Granade * Added some fixes from changes * Added most recommendations from Mathias * Added example to ApplySeriesOfOps * Added new examples * Added PermuteQubits example * Changed Swap Order to be appending to an array, added test for it * Updated ApplyOpRepeatedlyOver Docs * Added Mathias' comments * Reverted csproj file * Renamed TupleArrayAsNestedArray --- Standard/src/Arrays/Arrays.qs | 108 +++++++- .../Canon/Combinators/ApplyRepeatedOver.qs | 256 ++++++++++++++++++ Standard/src/Canon/CommonGates.qs | 28 ++ Standard/tests/ApplyRepeatedOverTests.qs | 67 +++++ Standard/tests/ArrayTests.qs | 42 +++ 5 files changed, 500 insertions(+), 1 deletion(-) create mode 100644 Standard/src/Canon/Combinators/ApplyRepeatedOver.qs create mode 100644 Standard/tests/ApplyRepeatedOverTests.qs diff --git a/Standard/src/Arrays/Arrays.qs b/Standard/src/Arrays/Arrays.qs index 064375e5512..740b4f195b4 100644 --- a/Standard/src/Arrays/Arrays.qs +++ b/Standard/src/Arrays/Arrays.qs @@ -2,8 +2,11 @@ // Licensed under the MIT License. namespace Microsoft.Quantum.Arrays { + open Microsoft.Quantum.Convert; open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Math; + open Microsoft.Quantum.Logical; /// # Summary /// Create an array that contains the same elements as an input array but in Reversed @@ -280,6 +283,109 @@ namespace Microsoft.Quantum.Arrays { return output; } -} + function _IsPermutationPred(permutation : Int[], value : Int) : Bool { + let index = IndexOf(EqualI(value, _), permutation); + return index != -1; + } + + function _IsPermutation(permuation : Int[]) : Bool { + return All(_IsPermutationPred(permuation, _), RangeAsIntArray(IndexRange(permuation))); + } + + /// # Summary + /// Returns the order elements in an array need to be swapped to produce an ordered array. + /// Assumes swaps occur in place. + /// + /// # Input + /// ## newOrder + /// Array with the permutation of the indices of the new array. There should be $n$ elements, + /// each being a unique integer from $0$ to $n-1$. + /// + /// # Output + /// The tuple represents the two indices to be swapped. The swaps begin at the lowest index. + /// + /// # Remarks + /// ## Example + /// ```qsharp + /// // The following returns [(0, 5),(0, 4),(0, 1),(0, 3)]; + /// let swapOrder = _SwapOrderToPermuteArray([5, 3, 2, 0, 1, 4]); + /// ``` + /// + /// ## Psuedocode + /// for (index in 0..Length(newOrder) - 1) + /// { + /// while newOrder[index] != index + /// { + /// Switch newOrder[index] with newOrder[newOrder[index]] + /// } + /// } + function _SwapOrderToPermuteArray(newOrder : Int[]) : (Int, Int)[] { + // Check to verify the new ordering actually is a permutation of the indices + Fact(_IsPermutation(newOrder), $"The new ordering is not a permutation"); + + mutable swaps = new (Int, Int)[0]; + mutable order = newOrder; + + // for each value, whenever the index and value don't match, swap until it does + for (index in IndexRange(order)) { + while (not EqualI(order[index], index)) + { + set swaps += [(index, order[index])]; + set order = Swapped(order[index], index, order); + } + } + + return swaps; + } + + /// # Summary + /// Applies a swap of two elements in an array. + /// + /// # Input + /// ## firstIndex + /// Index of the first element to be swapped. + /// + /// ## secondIndex + /// Index of the second element to be swapped. + /// + /// ## arr + /// Array with elements to be swapped. + /// + /// # Output + /// The array with the in place swapp applied. + /// + /// ## Example + /// ```qsharp + /// // The following returns [0, 3, 2, 1, 4] + /// Swapped(1, 3, [0, 1, 2, 3, 4]); + function Swapped<'T>(firstIndex: Int, secondIndex: Int, arr: 'T[]) : 'T[] { + return arr + w/ firstIndex <- arr[secondIndex] + w/ secondIndex <- arr[firstIndex]; + } + /// # Summary + /// Turns a list of 2-tuples into a nested array. + /// + /// # Input + /// ## tupleList + /// List of 2-tuples to be turned into a nested array. + /// + /// # Output + /// A nested array with length matching the tupleList. + /// + /// ## Example + /// ```qsharp + /// // The following returns [[2, 3], [4, 5]] + /// TupleArrayAsNestedArray([(2, 3), (4, 5)]); + /// ``` + function TupleArrayAsNestedArray<'T>(tupleList : ('T, 'T)[]) : 'T[][] { + mutable newArray = new 'T[][Length(tupleList)]; + for (idx in IndexRange(tupleList)) { + let (tupleLeft, tupleRight) = tupleList[idx]; + set newArray w/= idx <- [tupleLeft, tupleRight]; + } + return newArray; + } +} diff --git a/Standard/src/Canon/Combinators/ApplyRepeatedOver.qs b/Standard/src/Canon/Combinators/ApplyRepeatedOver.qs new file mode 100644 index 00000000000..7db8042d35e --- /dev/null +++ b/Standard/src/Canon/Combinators/ApplyRepeatedOver.qs @@ -0,0 +1,256 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Canon { + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Intrinsic; + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Helpers to repeatedly apply operations over qubit arrays + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// # Summary + /// Applies a list of ops and their targets sequentially on an array. + /// + /// # Input + /// ## listOfOps + /// List of ops, each taking a 'T array, to be applied. They are applied sequentially, lowest index first. + /// ## targets + /// Nested arrays describing the targets of the op. Each array should contain a list of ints describing + /// the indices to be used. + /// ## register + /// Qubit register to be acted upon. + /// + /// ## Example + /// // The following applies Exp([PauliX, PauliY], 0.5) to qubits 0, 1 + /// // then X to qubit 2 + /// let ops = [Exp([PauliX, PauliY], 0.5, _), ApplyToFirstQubit(X, _)]; + /// let indices = [[0, 1], [2]]; + /// ApplySeriesOfOps(ops, indices, qubitArray); + /// + /// # See Also + /// - Microsoft.Quantum.Canon.ApplyOpRepeatedlyOver + operation ApplySeriesOfOps<'T>(listOfOps : ('T[] => Unit)[], targets : Int[][], register : 'T[]) : Unit { + if (Length(listOfOps) != Length(targets)) { + fail "The number of ops and number of targets do not match!"; + } + for ((op, targetIndices) in Zip(listOfOps, targets)) { + if (Length(targetIndices) > Length(register)) { + fail "There are too many targets!"; + } + op(Subarray(targetIndices, register)); + } + } + + /// # Summary + /// Applies a list of ops and their targets sequentially on an array. (Adjoint) + /// + /// # Input + /// ## listOfOps + /// List of ops, each taking a 'T array, to be applied. They are applied sequentially, lowest index first. + /// Each must have an adjoint functor + /// ## targets + /// Nested arrays describing the targets of the op. Each array should contain a list of ints describing + /// the indices to be used. + /// ## register + /// Qubit register to be acted upon. + /// + /// ## Example + /// // The following applies Exp([PauliX, PauliY], 0.5) to qubits 0, 1 + /// // then X to qubit 2 + /// let ops = [Exp([PauliX, PauliY], 0.5, _), ApplyToFirstQubitA(X, _)]; + /// let indices = [[0, 1], [2]]; + /// ApplySeriesOfOpsA(ops, indices, qubitArray); + /// + /// # See Also + /// - Microsoft.Quantum.Canon.ApplyOpRepeatedlyOver + operation ApplySeriesOfOpsA<'T>(listOfOps : ('T[] => Unit is Adj)[], targets : Int[][], register : 'T[]) : Unit is Adj{ + if (Length(listOfOps) != Length(targets)) { + fail "The number of ops and number of targets do not match!"; + } + for ((op, targetIndices) in Zip(listOfOps, targets)) { + if (Length(targetIndices) > Length(register)) { + fail "There are too many targets!"; + } + op(Subarray(targetIndices, register)); + } + } + + /// # Summary + /// Applies a list of ops and their targets sequentially on an array. (Controlled) + /// + /// # Input + /// ## listOfOps + /// List of ops, each taking a 'T array, to be applied. They are applied sequentially, lowest index first. + /// Each must have a Controlled functor + /// ## targets + /// Nested arrays describing the targets of the op. Each array should contain a list of ints describing + /// the indices to be used. + /// ## register + /// Qubit register to be acted upon. + /// + /// ## Example + /// // The following applies Exp([PauliX, PauliY], 0.5) to qubits 0, 1 + /// // then X to qubit 2 + /// let ops = [Exp([PauliX, PauliY], 0.5, _), ApplyToFirstQubitC(X, _)]; + /// let indices = [[0, 1], [2]]; + /// ApplySeriesOfOpsC(ops, indices, qubitArray); + /// + /// # See Also + /// - Microsoft.Quantum.Canon.ApplyOpRepeatedlyOver + operation ApplySeriesOfOpsC<'T>(listOfOps : ('T[] => Unit is Ctl)[], targets : Int[][], register : 'T[]) : Unit is Ctl{ + if (Length(listOfOps) != Length(targets)) { + fail "The number of ops and number of targets do not match!"; + } + for ((op, targetIndices) in Zip(listOfOps, targets)) { + if (Length(targetIndices) > Length(register)) { + fail "There are too many targets!"; + } + op(Subarray(targetIndices, register)); + } + } + + /// # Summary + /// Applies a list of ops and their targets sequentially on an array. (Adjoint + Controlled) + /// + /// # Input + /// ## listOfOps + /// List of ops, each taking a 'T array, to be applied. They are applied sequentially, lowest index first. + /// Each must have both an Adjoint and Controlled functor. + /// ## targets + /// Nested arrays describing the targets of the op. Each array should contain a list of ints describing + /// the indices to be used. + /// ## register + /// Qubit register to be acted upon. + /// + /// ## Example + /// // The following applies Exp([PauliX, PauliY], 0.5) to qubits 0, 1 + /// // then X to qubit 2 + /// let ops = [Exp([PauliX, PauliY], 0.5, _), ApplyToFirstQubitCA(X, _)]; + /// let indices = [[0, 1], [2]]; + /// ApplySeriesOfOpsCA(ops, indices, qubitArray); + /// + /// # See Also + /// - Microsoft.Quantum.Canon.ApplyOpRepeatedlyOver + operation ApplySeriesOfOpsCA<'T>(listOfOps : ('T[] => Unit is Adj + Ctl)[], targets : Int[][], register : 'T[]) : Unit is Adj + Ctl{ + if (Length(listOfOps) != Length(targets)) { + fail "The number of ops and number of targets do not match!"; + } + for ((op, targetIndices) in Zip(listOfOps, targets)) { + if (Length(targetIndices) > Length(register)) { + fail "There are too many targets!"; + } + op(Subarray(targetIndices, register)); + } + } + + /// # Summary + /// Applies the same op over a qubit register multiple times. + /// + /// # Input + /// ## op + /// An operation to be applied multiple times on the qubit register + /// ## targets + /// Nested arrays describing the targets of the op. Each array should contain a list of ints describing + /// the qubits to be used. + /// ## register + /// Qubit register to be acted upon. + /// + /// # See Also + /// - Microsoft.Quantum.Canon.ApplySeriesOfOps + operation ApplyOpRepeatedlyOver(op : (Qubit[] => Unit), targets : Int[][], register : Qubit[]) : Unit + { + for (target in targets) + { + if (Length(target) > Length(register)) + { + fail "Too many targets!"; + } + let opTargets = Subarray(target, register); + op(opTargets); + } + } + + /// # Summary + /// Applies the same op over a qubit register multiple times. + /// + /// # Input + /// ## op + /// An operation to be applied multiple times on the qubit register + /// ## targets + /// Nested arrays describing the targets of the op. Each array should contain a list of ints describing + /// the qubits to be used. + /// ## register + /// Qubit register to be acted upon. + /// + /// # See Also + /// - Microsoft.Quantum.Canon.ApplySeriesOfOps + operation ApplyOpRepeatedlyOverA(op : (Qubit[] => Unit is Adj), targets : Int[][], register : Qubit[]) : Unit is Adj + { + for (target in targets) + { + if (Length(target) > Length(register)) + { + fail "Too many targets!"; + } + let opTargets = Subarray(target, register); + op(opTargets); + } + } + + /// # Summary + /// Applies the same op over a qubit register multiple times. + /// + /// # Input + /// ## op + /// An operation to be applied multiple times on the qubit register + /// ## targets + /// Nested arrays describing the targets of the op. Each array should contain a list of ints describing + /// the qubits to be used. + /// ## register + /// Qubit register to be acted upon. + /// + /// # See Also + /// - Microsoft.Quantum.Canon.ApplySeriesOfOps + operation ApplyOpRepeatedlyOverC(op : (Qubit[] => Unit is Ctl), targets : Int[][], register : Qubit[]) : Unit is Ctl + { + for (target in targets) + { + if (Length(target) > Length(register)) + { + fail "Too many targets!"; + } + let opTargets = Subarray(target, register); + op(opTargets); + } + } + + /// # Summary + /// Applies the same op over a qubit register multiple times. + /// + /// # Input + /// ## op + /// An operation to be applied multiple times on the qubit register + /// ## targets + /// Nested arrays describing the targets of the op. Each array should contain a list of ints describing + /// the qubits to be used. + /// ## register + /// Qubit register to be acted upon. + /// + /// # See Also + /// - Microsoft.Quantum.Canon.ApplySeriesOfOps + operation ApplyOpRepeatedlyOverCA(op : (Qubit[] => Unit is Adj+Ctl), targets : Int[][], register : Qubit[]) : Unit is Adj+Ctl + { + for (target in targets) + { + if (Length(target) > Length(register)) + { + fail "Too many targets!"; + } + let opTargets = Subarray(target, register); + op(opTargets); + } + } + +} diff --git a/Standard/src/Canon/CommonGates.qs b/Standard/src/Canon/CommonGates.qs index e3f74f10a2c..fafc2dcd31f 100644 --- a/Standard/src/Canon/CommonGates.qs +++ b/Standard/src/Canon/CommonGates.qs @@ -4,6 +4,8 @@ namespace Microsoft.Quantum.Canon { open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Arrays; /// # Summary /// Applies the controlled-X (CX) gate to a pair of qubits. @@ -272,4 +274,30 @@ namespace Microsoft.Quantum.Canon { CZ(qubit1, qubit2); } + /// # Summary + /// Permutes qubits by using the SWAP operation. + /// + /// # Input + /// ## ordering + /// Describes the new ordering of the qubits, where the qubit at index i will now be at ordering[i]. + /// ## register + /// Qubit register to be acted upon. + /// + /// # Example + /// Given ordering = [2, 1, 0] and register $\ket{\alpha_0} \ket{\alpha_1} \ket{\alpha_2}$, PermuteQubits + /// changes the register into $\ket{\alpha_2} \ket{\alpha_1} \ket{\alpha_0}$ + /// + /// ```qsharp + /// // The following two lines are equivalent + /// PermuteQubits([2, 1, 0], register); + /// SWAP(register[0], register[2]); + /// ``` + operation PermuteQubits(ordering : Int[], register : Qubit[]) : Unit is Adj+Ctl { + EqualityFactI(Length(ordering), Length(register), "The new ordering has an incorrect number of elements"); + + for ((left, right) in _SwapOrderToPermuteArray(ordering)) { + SWAP(register[left], register[right]); + } + } + } diff --git a/Standard/tests/ApplyRepeatedOverTests.qs b/Standard/tests/ApplyRepeatedOverTests.qs new file mode 100644 index 00000000000..31cdc1ca0fb --- /dev/null +++ b/Standard/tests/ApplyRepeatedOverTests.qs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Tests { + + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Arrays; + + + operation ApplySeriesOfOpsTest() : Unit { + // create the sample ops + their targets here + let op1 = ApplyToFirstQubit(X, _); + let op2 = ApplyToFirstTwoQubits(CNOT, _); + let op3 = Exp([PauliX, PauliZ, PauliY], 0.2002, _); + let op4 = ApplyToEachA(H, _); + let target1 = [0]; + let target2 = [0, 4]; + let target3 = [2, 3, 5]; + let target4 = [1, 2, 3, 4]; + + let listOfOps = [op1, op2, op3, op4]; + let listOfTargets = [target1, target2, target3, target4]; + AssertOperationsEqualReferenced(6, ApplySeriesOfOps(listOfOps, listOfTargets, _), _SampleApplySeriesOfOps(_)); + } + + // Helper method for ApplySeriesOfOpsTest + operation _SampleApplySeriesOfOps(register : Qubit[]) : Unit is Adj + Ctl { + // replicate those ops implemented here + X(register[0]); + CNOT(register[0], register[4]); + Exp([PauliX, PauliZ, PauliY], 0.2002, Subarray([2, 3, 5], register)); + ApplyToEachCA(H, Subarray([1, 2, 3, 4], register)); + } + + operation ApplyRepeatedOpTest() : Unit { + let op = ApplyToFirstThreeQubits(CCNOT, _); + let targets = [[0, 1, 2], [2, 1, 0], [3, 4, 5], [2, 4, 0], [5, 3, 1]]; + AssertOperationsEqualReferenced(6, ApplyOpRepeatedlyOver(op, targets, _), _SampleApplyRepeatedOp(_)); + } + + // Helper method for ApplyRepeatedOpTest + operation _SampleApplyRepeatedOp(register : Qubit[]) : Unit is Adj + Ctl { + CCNOT(register[0], register[1], register[2]); + CCNOT(register[2], register[1], register[0]); + CCNOT(register[3], register[4], register[5]); + CCNOT(register[2], register[4], register[0]); + CCNOT(register[5], register[3], register[1]); + } + + operation PermuteQubitsTest() : Unit { + let sampleOrder = [5, 3, 2, 0, 1, 4]; + AssertOperationsEqualReferenced(6, PermuteQubits(sampleOrder, _) , _SamplePermuteQubits); + } + + // Helper method for PermuteQubitsTest + operation _SamplePermuteQubits(register : Qubit[]) : Unit is Adj + Ctl { + // assumes the order to be swapped is [(0, 5),(0, 4),(0, 1),(0, 3)] + // (Order is [5, 3, 2, 0, 1, 4]) + SWAP(register[0], register[5]); + SWAP(register[0], register[4]); + SWAP(register[0], register[1]); + SWAP(register[0], register[3]); + } + +} diff --git a/Standard/tests/ArrayTests.qs b/Standard/tests/ArrayTests.qs index 528c2f968ba..0d45adec9fc 100644 --- a/Standard/tests/ArrayTests.qs +++ b/Standard/tests/ArrayTests.qs @@ -153,6 +153,48 @@ namespace Microsoft.Quantum.Tests { } } + function _SwapOrderToPermuteArrayTest() : Unit { + let newOrder = [0, 4, 2, 1, 3]; + let expected = [(1, 4), (1, 3)]; + let actual = _SwapOrderToPermuteArray(newOrder); + + EqualityFactI(Length(expected), Length(actual), "Number of swaps does not match"); + for ((exp, act) in Zip(expected, actual)) { + let (leftExp, rightExp) = exp; + let (leftAct, rightAct) = act; + + EqualityFactI(leftExp, leftAct, "SWAP doesn't match"); + EqualityFactI(rightExp, rightAct, "SWAP doesn't match"); + } + } + + function SwappedTest() : Unit { + let example = [2, 4, 6, 8, 10]; + let expected = [2, 8, 6, 4, 10]; + let leftIndex = 1; + let rightIndex = 3; + let newArray = Swapped(leftIndex, rightIndex, example); + + EqualityFactI(Length(expected), Length(newArray), "Swapped array is a different size than original"); + for ((exp, act) in Zip(expected, newArray)) { + EqualityFactI(exp, act, "Elements did not match"); + } + } + + function TupleArrayAsNestedArrayTest() : Unit { + let example = [(0, 1), (2, 3), (4, 5), (6, 7)]; + let expected = [[0, 1], [2, 3], [4, 5], [6, 7]]; + + let actual = TupleArrayAsNestedArray(example); + EqualityFactI(Length(expected), Length(actual), "Arrays are of different sizes"); + for ((exp, act) in Zip(expected, actual)) { + for ((elementExp, elementAct) in Zip(exp, act)) { + EqualityFactI(elementExp, elementAct, "Elements did not match"); + } + } + + } + }