diff --git a/Standard/src/Arrays/Arrays.qs b/Standard/src/Arrays/Arrays.qs index 39ee97b74a3..71bb8be81c7 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 @@ -295,5 +298,109 @@ namespace Microsoft.Quantum.Arrays { } + 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 0cf9e169289..256b42468dd 100644 --- a/Standard/tests/ArrayTests.qs +++ b/Standard/tests/ArrayTests.qs @@ -5,61 +5,61 @@ namespace Microsoft.Quantum.Tests { open Microsoft.Quantum.Canon; open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Arrays; - - + + function ZipTest () : Unit { - + let left = [1, 2, 101]; let right = [PauliY, PauliI]; let zipped = Zip(left, right); let (leftActual1, rightActual1) = zipped[0]; - + if (leftActual1 != 1 or rightActual1 != PauliY) { fail $"Expected (1, PauliY), got ({leftActual1}, {rightActual1})."; } - + let (leftActual2, rightActual2) = zipped[1]; - + if (leftActual2 != 2 or rightActual2 != PauliI) { fail $"Expected (2, PauliI), got ({leftActual2}, {rightActual2})."; } } - - + + function LookupTest () : Unit { - + let array = [1, 12, 71, 103]; let fn = LookupFunction(array); EqualityFactI(fn(0), 1, $"fn(0) did not return array[0]"); - + // Make sure we can call in random order! EqualityFactI(fn(3), 103, $"fn(3) did not return array[3]"); EqualityFactI(fn(2), 71, $"fn(2) did not return array[2]"); EqualityFactI(fn(1), 12, $"fn(1) did not return array[1]"); } - - + + function ConstantArrayTestHelper (x : Int) : Int { - + return x * x; } - - + + function ConstantArrayTest () : Unit { - + let dblArray = ConstantArray(71, 2.17); EqualityFactI(Length(dblArray), 71, $"ConstantArray(Int, Double) had the wrong length."); let ignore = Mapped(NearEqualityFactD(_, 2.17), dblArray); - + // Stress test by making an array of Int -> Int. let fnArray = ConstantArray(7, ConstantArrayTestHelper); EqualityFactI(Length(fnArray), 7, $"ConstantArray(Int, Int -> Int) had the wrong length."); EqualityFactI(fnArray[3](7), 49, $"ConstantArray(Int, Int -> Int) had the wrong value."); } - - + + function SubarrayTest () : Unit { - + let array0 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let subarrayOdd = Subarray([1, 3, 5, 7, 9], array0); let subarrayEven = Subarray([0, 2, 4, 6, 8, 10], array0); @@ -68,34 +68,34 @@ namespace Microsoft.Quantum.Tests { let array1 = [10, 11, 12, 13]; Ignore(Mapped(EqualityFactI(_, _, $"Subarray failed: subpermutation case."), Zip([12, 11], Subarray([2, 1], array1)))); } - - + + function FilterTest () : Unit { - + let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let evenArray = Filtered(IsEven, array); EqualityFactB(All(IsEven, evenArray), true, $"the even elements of [1..10] were not correctly filtered."); } - - + + function ReverseTest () : Unit { - + let array = [1, 2, 3]; Ignore(Mapped(EqualityFactI(_, _, $"Reversed failed."), Zip([3, 2, 1], Reversed(array)))); } - - + + function ExcludeTest () : Unit { - + let array = [10, 11, 12, 13, 14, 15]; Ignore(Mapped(EqualityFactI(_, _, $"Exclude failed."), Zip([10, 11, 13, 14], Exclude([2, 5], array)))); } - - + + function PadTest () : Unit { - + mutable arrayTestCase = [(-5, 2, [10, 11, 12], [10, 11, 12, 2, 2]), (5, 2, [10, 11, 12], [2, 2, 10, 11, 12]), (-3, -2, [10, 11, 12], [10, 11, 12])]; - + for (idxTest in IndexRange(arrayTestCase)) { let (nElementsTotal, defaultElement, inputArray, outputArray) = arrayTestCase[idxTest]; let paddedArray = Padded(nElementsTotal, defaultElement, inputArray); @@ -161,6 +161,47 @@ namespace Microsoft.Quantum.Tests { Fact(not IsEmpty([""]), "Non-empty array marked as empty."); } + 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"); + } + } + } + }