From 57eaf0d143ded179dfc64bca0ac924243a857254 Mon Sep 17 00:00:00 2001 From: Vincent van Wingerden <25651976+vivanwin@users.noreply.github.com> Date: Thu, 19 Mar 2020 08:02:34 +0100 Subject: [PATCH] [BasicGates] Improve error messaging (#300) Per #176 Co-authored-by: Mariia Mykhailova --- BasicGates/BasicGates.ipynb | 10 +- BasicGates/ReferenceImplementation.qs | 10 +- BasicGates/Tasks.qs | 10 +- BasicGates/Tests.qs | 160 ++++++++++++++++++++------ 4 files changed, 138 insertions(+), 52 deletions(-) diff --git a/BasicGates/BasicGates.ipynb b/BasicGates/BasicGates.ipynb index 0542f050c44..ae7c7a6db0d 100644 --- a/BasicGates/BasicGates.ipynb +++ b/BasicGates/BasicGates.ipynb @@ -396,7 +396,7 @@ "source": [ "%kata T201_TwoQubitGate1_Test\n", "\n", - "operation TwoQubitGate1 (qs : Qubit[]) : Unit is Adj {\n", + "operation TwoQubitGate1 (qs : Qubit[]) : Unit is Adj+Ctl {\n", " // ...\n", "}" ] @@ -424,7 +424,7 @@ "source": [ "%kata T202_TwoQubitGate2_Test\n", "\n", - "operation TwoQubitGate2 (qs : Qubit[]) : Unit is Adj {\n", + "operation TwoQubitGate2 (qs : Qubit[]) : Unit is Adj+Ctl {\n", " // ...\n", "}" ] @@ -451,7 +451,7 @@ "source": [ "%kata T203_TwoQubitGate3_Test\n", "\n", - "operation TwoQubitGate3 (qs : Qubit[]) : Unit is Adj {\n", + "operation TwoQubitGate3 (qs : Qubit[]) : Unit is Adj+Ctl {\n", " // ...\n", "}" ] @@ -476,7 +476,7 @@ "source": [ "%kata T204_ToffoliGate_Test\n", "\n", - "operation ToffoliGate (qs : Qubit[]) : Unit is Adj {\n", + "operation ToffoliGate (qs : Qubit[]) : Unit is Adj+Ctl {\n", " // ...\n", "}" ] @@ -501,7 +501,7 @@ "source": [ "%kata T205_FredkinGate_Test\n", "\n", - "operation FredkinGate (qs : Qubit[]) : Unit is Adj {\n", + "operation FredkinGate (qs : Qubit[]) : Unit is Adj+Ctl {\n", " // ...\n", "}" ] diff --git a/BasicGates/ReferenceImplementation.qs b/BasicGates/ReferenceImplementation.qs index a43fa6b0922..fec42578c80 100644 --- a/BasicGates/ReferenceImplementation.qs +++ b/BasicGates/ReferenceImplementation.qs @@ -129,7 +129,7 @@ namespace Quantum.Kata.BasicGates { // Note that unless the starting state of the first qubit was |0⟩ or |1⟩, // the resulting two-qubit state can not be represented as a tensor product // of the states of individual qubits any longer; thus the qubits become entangled. - operation TwoQubitGate1_Reference (qs : Qubit[]) : Unit is Adj { + operation TwoQubitGate1_Reference (qs : Qubit[]) : Unit is Adj+Ctl { CNOT(qs[0], qs[1]); } @@ -140,7 +140,7 @@ namespace Quantum.Kata.BasicGates { // Goal: Change the two-qubit state to (|00⟩ + |01⟩ + |10⟩ - |11⟩) / 2. // Note that while the starting state can be represented as a tensor product of single-qubit states, // the resulting two-qubit state can not be represented in such a way. - operation TwoQubitGate2_Reference (qs : Qubit[]) : Unit is Adj { + operation TwoQubitGate2_Reference (qs : Qubit[]) : Unit is Adj+Ctl { Controlled Z([qs[0]], qs[1]); // alternatively: CZ(qs[0], qs[1]); } @@ -149,7 +149,7 @@ namespace Quantum.Kata.BasicGates { // Input: Two qubits (stored in an array of length 2) in an arbitrary // two-qubit state α|00⟩ + β|01⟩ + γ|10⟩ + δ|11⟩. // Goal: Change the two-qubit state to α|00⟩ + γ|01⟩ + β|10⟩ + δ|11⟩. - operation TwoQubitGate3_Reference (qs : Qubit[]) : Unit is Adj { + operation TwoQubitGate3_Reference (qs : Qubit[]) : Unit is Adj+Ctl { // Hint: this task can be solved using one intrinsic gate; // as an exercise, try to express the solution using several controlled Pauli gates. CNOT(qs[0], qs[1]); @@ -164,7 +164,7 @@ namespace Quantum.Kata.BasicGates { // Goal: Flip the state of the third qubit if the state of the first two is |11⟩: // i.e., change the three-qubit state to // α|000⟩ + β|001⟩ + γ|010⟩ + δ|011⟩ + ε|100⟩ + ζ|101⟩ + θ|110⟩ + η|111⟩. - operation ToffoliGate_Reference (qs : Qubit[]) : Unit is Adj { + operation ToffoliGate_Reference (qs : Qubit[]) : Unit is Adj+Ctl { CCNOT(qs[0], qs[1], qs[2]); // alternatively (Controlled X)(qs[0..1], qs[2]); } @@ -175,7 +175,7 @@ namespace Quantum.Kata.BasicGates { // Goal: Swap the states of second and third qubit if and only if the state of the first qubit is |1⟩: // i.e., change the three-qubit state to // α|000⟩ + β|001⟩ + γ|010⟩ + δ|011⟩ + ε|100⟩ + η|101⟩ + ζ|110⟩ + θ|111⟩. - operation FredkinGate_Reference (qs : Qubit[]) : Unit is Adj { + operation FredkinGate_Reference (qs : Qubit[]) : Unit is Adj+Ctl { Controlled SWAP([qs[0]], (qs[1], qs[2])); } diff --git a/BasicGates/Tasks.qs b/BasicGates/Tasks.qs index da426062e2c..494de48e8a9 100644 --- a/BasicGates/Tasks.qs +++ b/BasicGates/Tasks.qs @@ -162,7 +162,7 @@ namespace Quantum.Kata.BasicGates { // Note that unless the starting state of the first qubit was |0⟩ or |1⟩, // the resulting two-qubit state can not be represented as a tensor product // of the states of individual qubits any longer; thus the qubits become entangled. - operation TwoQubitGate1 (qs : Qubit[]) : Unit is Adj { + operation TwoQubitGate1 (qs : Qubit[]) : Unit is Adj+Ctl { // ... } @@ -173,7 +173,7 @@ namespace Quantum.Kata.BasicGates { // Goal: Change the two-qubit state to (|00⟩ + |01⟩ + |10⟩ - |11⟩) / 2. // Note that while the starting state can be represented as a tensor product of single-qubit states, // the resulting two-qubit state can not be represented in such a way. - operation TwoQubitGate2 (qs : Qubit[]) : Unit is Adj { + operation TwoQubitGate2 (qs : Qubit[]) : Unit is Adj+Ctl { // ... } @@ -182,7 +182,7 @@ namespace Quantum.Kata.BasicGates { // Input: Two qubits (stored in an array of length 2) in an arbitrary // two-qubit state α|00⟩ + β|01⟩ + γ|10⟩ + δ|11⟩. // Goal: Change the two-qubit state to α|00⟩ + γ|01⟩ + β|10⟩ + δ|11⟩. - operation TwoQubitGate3 (qs : Qubit[]) : Unit is Adj { + operation TwoQubitGate3 (qs : Qubit[]) : Unit is Adj+Ctl { // Hint: this task can be solved using one intrinsic gate; // as an exercise, try to express the solution using several // (possibly controlled) Pauli gates. @@ -196,7 +196,7 @@ namespace Quantum.Kata.BasicGates { // Goal: Flip the state of the third qubit if the state of the first two is |11⟩: // i.e., change the three-qubit state to // α|000⟩ + β|001⟩ + γ|010⟩ + δ|011⟩ + ε|100⟩ + ζ|101⟩ + θ|110⟩ + η|111⟩. - operation ToffoliGate (qs : Qubit[]) : Unit is Adj { + operation ToffoliGate (qs : Qubit[]) : Unit is Adj+Ctl { // ... } @@ -206,7 +206,7 @@ namespace Quantum.Kata.BasicGates { // α|000⟩ + β|001⟩ + γ|010⟩ + δ|011⟩ + ε|100⟩ + ζ|101⟩ + η|110⟩ + θ|111⟩. // Goal: Swap the states of second and third qubit if and only if the state of the first qubit is |1⟩: // α|000⟩ + β|001⟩ + γ|010⟩ + δ|011⟩ + ε|100⟩ + η|101⟩ + ζ|110⟩ + θ|111⟩. - operation FredkinGate (qs : Qubit[]) : Unit is Adj { + operation FredkinGate (qs : Qubit[]) : Unit is Adj+Ctl { // ... } diff --git a/BasicGates/Tests.qs b/BasicGates/Tests.qs index 5a6ee84ae6c..982643e6a38 100644 --- a/BasicGates/Tests.qs +++ b/BasicGates/Tests.qs @@ -26,59 +26,137 @@ namespace Quantum.Kata.BasicGates { // so the tests use controlled version of the operations which converts the global phase into a relative phase // and makes it possible to detect. + // ------------------------------------------------------ + // Helper wrapper to represent operation on one qubit + // as an operation on an array of one qubits + operation ArrayWrapper (op : (Qubit => Unit is Adj+Ctl), qs : Qubit[]) : Unit is Adj+Ctl { + op(qs[0]); + } + // ------------------------------------------------------ // Helper wrapper to represent controlled variant of operation on one qubit // as an operation on an array of two qubits - operation ArrayWrapperOperation (op : (Qubit => Unit is Adj+Ctl), qs : Qubit[]) : Unit is Adj+Ctl { + operation ArrayWrapperControlled (op : (Qubit => Unit is Adj+Ctl), qs : Qubit[]) : Unit is Adj+Ctl { Controlled op([qs[0]], qs[1]); } - - + + + // ------------------------------------------------------ + // Helper operation to show the difference between the reference solution and the learner's one + operation DumpDiff (N : Int, + statePrep : (Qubit[] => Unit is Adj+Ctl), + testImpl : (Qubit[] => Unit is Adj+Ctl), + refImpl : (Qubit[] => Unit is Adj+Ctl) + ) : Unit { + using (qs = Qubit[N]) { + // Prepare the input state and show it + statePrep(qs); + Message("The starting state:"); + DumpMachine(); + + // Apply the reference solution and show result + refImpl(qs); + Message("The desired state:"); + DumpMachine(); + ResetAll(qs); + + // Prepare the input state again for test implementation + statePrep(qs); + // Apply learner's solution and show result + testImpl(qs); + Message("The actual state:"); + DumpMachine(); + ResetAll(qs); + } + } + + + // Used for single-qubit operations that are unlikely to introduce the extra global phase + operation DumpDiffOnOneQubit (testImpl : (Qubit => Unit is Adj+Ctl), + refImpl : (Qubit => Unit is Adj+Ctl)) : Unit { + DumpDiff(1, ArrayWrapper(Ry(2.0 * ArcCos(0.6), _), _), + ArrayWrapper(testImpl, _), + ArrayWrapper(refImpl, _)); + } + + // ------------------------------------------------------ operation T101_StateFlip_Test () : Unit { - AssertOperationsEqualReferenced(2, ArrayWrapperOperation(StateFlip, _), ArrayWrapperOperation(StateFlip_Reference, _)); + DumpDiffOnOneQubit(StateFlip, StateFlip_Reference); + AssertOperationsEqualReferenced(2, ArrayWrapperControlled(StateFlip, _), + ArrayWrapperControlled(StateFlip_Reference, _)); } // ------------------------------------------------------ operation T102_BasisChange_Test () : Unit { - AssertOperationsEqualReferenced(2, ArrayWrapperOperation(BasisChange, _), ArrayWrapperOperation(BasisChange_Reference, _)); + DumpDiffOnOneQubit(BasisChange, BasisChange_Reference); + AssertOperationsEqualReferenced(2, ArrayWrapperControlled(BasisChange, _), + ArrayWrapperControlled(BasisChange_Reference, _)); } // ------------------------------------------------------ operation T103_SignFlip_Test () : Unit { - AssertOperationsEqualReferenced(2, ArrayWrapperOperation(SignFlip, _), ArrayWrapperOperation(SignFlip_Reference, _)); + DumpDiffOnOneQubit(SignFlip, SignFlip_Reference); + AssertOperationsEqualReferenced(2, ArrayWrapperControlled(SignFlip, _), + ArrayWrapperControlled(SignFlip_Reference, _)); } // ------------------------------------------------------ operation T104_AmplitudeChange_Test () : Unit { + // pick one rotation angle on which to show difference between solutions + let dumpAlpha = ((2.0 * PI()) * IntAsDouble(6)) / 36.0; + Message($"Applying amplitude change with alpha = {dumpAlpha}"); + DumpDiffOnOneQubit(AmplitudeChange(dumpAlpha, _), AmplitudeChange_Reference(dumpAlpha, _)); + for (i in 0 .. 36) { let alpha = ((2.0 * PI()) * IntAsDouble(i)) / 36.0; - AssertOperationsEqualReferenced(2, ArrayWrapperOperation(AmplitudeChange(alpha, _), _), ArrayWrapperOperation(AmplitudeChange_Reference(alpha, _), _)); + AssertOperationsEqualReferenced(2, ArrayWrapperControlled(AmplitudeChange(alpha, _), _), + ArrayWrapperControlled(AmplitudeChange_Reference(alpha, _), _)); } } // ------------------------------------------------------ operation T105_PhaseFlip_Test () : Unit { - AssertOperationsEqualReferenced(2, ArrayWrapperOperation(PhaseFlip, _), ArrayWrapperOperation(PhaseFlip_Reference, _)); + DumpDiffOnOneQubit(PhaseFlip, PhaseFlip_Reference); + AssertOperationsEqualReferenced(2, ArrayWrapperControlled(PhaseFlip, _), + ArrayWrapperControlled(PhaseFlip_Reference, _)); } // ------------------------------------------------------ operation T106_PhaseChange_Test () : Unit { + let dumpAlpha = ((2.0 * PI()) * IntAsDouble(10)) / 36.0; + Message($"Applying phase change with alpha = {dumpAlpha}"); + DumpDiffOnOneQubit(PhaseChange(dumpAlpha,_), PhaseChange_Reference(dumpAlpha,_)); + for (i in 0 .. 36) { let alpha = ((2.0 * PI()) * IntAsDouble(i)) / 36.0; - AssertOperationsEqualReferenced(2, ArrayWrapperOperation(PhaseChange(alpha, _), _), ArrayWrapperOperation(PhaseChange_Reference(alpha, _), _)); + AssertOperationsEqualReferenced(2, ArrayWrapperControlled(PhaseChange(alpha, _), _), + ArrayWrapperControlled(PhaseChange_Reference(alpha, _), _)); } } // ------------------------------------------------------ + // State prep for showing the controlled version of single-qubit operation + operation StatePrepForControlled (qs : Qubit[]) : Unit is Adj+Ctl { + H(qs[0]); + Ry(2.0 * ArcCos(0.6), qs[1]); + } + operation T107_GlobalPhaseChange_Test () : Unit { - AssertOperationsEqualReferenced(2, ArrayWrapperOperation(GlobalPhaseChange, _), ArrayWrapperOperation(GlobalPhaseChange_Reference, _)); + // use the controlled version of unitaries for showing the difference, since it's hard to observe on non-controlled versions + Message("Showing effect of controlled-GlobalPhaseChange"); + DumpDiff(2, StatePrepForControlled, + ArrayWrapperControlled(GlobalPhaseChange, _), + ArrayWrapperControlled(GlobalPhaseChange_Reference, _)); + + AssertOperationsEqualReferenced(2, ArrayWrapperControlled(GlobalPhaseChange, _), + ArrayWrapperControlled(GlobalPhaseChange_Reference, _)); } @@ -126,45 +204,46 @@ namespace Quantum.Kata.BasicGates { } } - // ------------------------------------------------------ operation T108_BellStateChange1_Test () : Unit { + DumpDiff(2, StatePrep_BellState(_, 0), BellStateChange1, BellStateChange1_Reference); VerifyBellStateConversion(BellStateChange1, 0, 1); } // ------------------------------------------------------ operation T109_BellStateChange2_Test () : Unit { + DumpDiff(2, StatePrep_BellState(_, 0), BellStateChange2, BellStateChange2_Reference); VerifyBellStateConversion(BellStateChange2, 0, 2); } // ------------------------------------------------------ operation T110_BellStateChange3_Test () : Unit { + DumpDiff(2, StatePrep_BellState(_, 0), BellStateChange3, BellStateChange3_Reference); + Message("If the desired and the actual states match but the test doesn't pass, check whether your solution introduces a global phase; it shouldn't!"); VerifyBellStateConversion(BellStateChange3, 0, 3); } // ------------------------------------------------------ - // prepare state |A⟩ = cos(α) * |0⟩ + sin(α) * |1⟩ - operation StatePrep_A (alpha : Double, q : Qubit) : Unit is Adj { - Ry(2.0 * alpha, q); + operation StatePrepRy (qs : Qubit[]) : Unit is Adj+Ctl { + Ry(2.0 * (2.0 * PI() * 6.0) / 36.0, Head(qs)); } - - - // ------------------------------------------------------ + operation T201_TwoQubitGate1_Test () : Unit { - + DumpDiff(2, StatePrepRy, TwoQubitGate1, TwoQubitGate1_Reference); + // Note that the way the problem is formulated, we can't just compare two unitaries, - // we need to create an input state |A⟩ and check that the output state is correct + // we need to create a specific input state and check that the output state is correct using (qs = Qubit[2]) { for (i in 0 .. 36) { let alpha = ((2.0 * PI()) * IntAsDouble(i)) / 36.0; within { - // prepare A state - StatePrep_A(alpha, qs[0]); + // prepare state cos(α) * |0⟩ + sin(α) * |1⟩ + Ry(2.0 * alpha, qs[0]); } apply { // apply operation that needs to be tested @@ -181,25 +260,20 @@ namespace Quantum.Kata.BasicGates { } - // ------------------------------------------------------ - // prepare state |+⟩ ⊗ |+⟩ = (|00⟩ + |01⟩ + |10⟩ + |11⟩) / 2. - operation StatePrep_PlusPlus (qs : Qubit[]) : Unit is Adj { - ApplyToEachA(H, qs); - } - - // ------------------------------------------------------ operation T202_TwoQubitGate2_Test () : Unit { + DumpDiff(2, ApplyToEachCA(H, _), TwoQubitGate2, TwoQubitGate2_Reference); using (qs = Qubit[2]) { - // prepare |+⟩ ⊗ |+⟩ state - StatePrep_PlusPlus(qs); - - // apply operation that needs to be tested - TwoQubitGate2(qs); + within { + // prepare |+⟩ ⊗ |+⟩ state + ApplyToEachCA(H, qs); + } apply { + // apply operation that needs to be tested + TwoQubitGate2(qs); - // apply adjoint reference operation and adjoint of state prep - Adjoint TwoQubitGate2_Reference(qs); - Adjoint StatePrep_PlusPlus(qs); + // apply adjoint reference operation + Adjoint TwoQubitGate2_Reference(qs); + } // assert that all qubits end up in |0⟩ state AssertAllZero(qs); @@ -207,6 +281,16 @@ namespace Quantum.Kata.BasicGates { } + // ------------------------------------------------------ + // Prepare a state for tests 2.3-2.5 + operation StatePrepMiscAmplitudes (qs : Qubit[]) : Unit is Adj+Ctl { + let alphas = [5.0, 10.0, 15.0]; + for (index in 0 .. Length(qs) - 1) { + Ry(2.0 * (alphas[index] + IntAsDouble(index + 1)), qs[index]); + } + } + + // ------------------------------------------------------ operation SwapWrapper (qs : Qubit[]) : Unit is Adj { SWAP(qs[0], qs[1]); @@ -214,6 +298,7 @@ namespace Quantum.Kata.BasicGates { operation T203_TwoQubitGate3_Test () : Unit { + DumpDiff(2, StatePrepMiscAmplitudes, TwoQubitGate3, TwoQubitGate3_Reference); AssertOperationsEqualReferenced(2, SwapWrapper, TwoQubitGate3_Reference); AssertOperationsEqualReferenced(2, TwoQubitGate3, TwoQubitGate3_Reference); } @@ -221,13 +306,14 @@ namespace Quantum.Kata.BasicGates { // ------------------------------------------------------ operation T204_ToffoliGate_Test () : Unit { + DumpDiff(3, StatePrepMiscAmplitudes, ToffoliGate, ToffoliGate_Reference); AssertOperationsEqualReferenced(3, ToffoliGate, ToffoliGate_Reference); } // ------------------------------------------------------ operation T205_FredkinGate_Test () : Unit { + DumpDiff(3, StatePrepMiscAmplitudes, FredkinGate, FredkinGate_Reference); AssertOperationsEqualReferenced(3, FredkinGate, FredkinGate_Reference); } - }