diff --git a/katas/content/index.json b/katas/content/index.json index 92dce01f05..0a1e08edef 100644 --- a/katas/content/index.json +++ b/katas/content/index.json @@ -10,6 +10,7 @@ "single_qubit_measurements", "multi_qubit_measurements", "random_numbers", + "superdense_coding", "oracles", "deutsch_algo", "deutsch_jozsa", diff --git a/katas/content/superdense_coding/Common.qs b/katas/content/superdense_coding/Common.qs new file mode 100644 index 0000000000..0d7a8a4849 --- /dev/null +++ b/katas/content/superdense_coding/Common.qs @@ -0,0 +1,75 @@ +namespace Kata.Verification { + operation CreateEntangledPairWrapper_Reference(qs : Qubit[]) : Unit is Adj + Ctl { + let (qAlice, qBob) = (qs[0], qs[1]); + H(qAlice); + CNOT(qAlice, qBob); + } + + operation EncodeMessageInQubit_Reference(qAlice : Qubit, message : (Bool, Bool)) : Unit { + let (bit1, bit2) = message; + + if bit1 { + Z(qAlice); + } + + if bit2 { + X(qAlice); + } + } + + operation DecodeMessageFromQubits_Reference(qAlice : Qubit, qBob : Qubit) : (Bool, Bool) { + CNOT(qAlice, qBob); + H(qAlice); + return (MResetZ(qAlice) == One, MResetZ(qBob) == One); + } + + + // ------------------------------------------------------ + // Helper operation that runs superdense coding protocol using two building blocks + // specified as first two parameters. + operation ComposeProtocol( + encodeOp : ((Qubit, (Bool, Bool)) => Unit), + decodeOp : ((Qubit, Qubit) => (Bool, Bool)), + message : (Bool, Bool) + ) : (Bool, Bool) { + + use (qAlice, qBob) = (Qubit(), Qubit()); + + CreateEntangledPairWrapper_Reference([qAlice, qBob]); + + encodeOp(qAlice, message); + + let (bit1, bit2) = decodeOp(qAlice, qBob); + + ResetAll([qAlice, qBob]); + + return (bit1, bit2); + } + + // ------------------------------------------------------ + // Helper operation that runs superdense coding protocol (specified by protocolOp) + // on all possible input values and verifies that decoding result matches the inputs + operation CheckProtocolWithFeedback(protocolOp : ((Bool, Bool) => (Bool, Bool))) : Bool { + + // Loop over the 4 possible combinations of two bits + for n in 0..3 { + let data = (1 == n / 2, 1 == n % 2); + let (dataBit1, dataBit2) = data; + + for iter in 1..100 { + let (bit1, bit2) = protocolOp(data); + + // Now test if the bits were transfered correctly. + if not (bit1 == dataBit1 and bit2 == dataBit2) { + Message("Incorrect."); + Message($"({dataBit1}, {dataBit2}) was transfered incorrectly as ({bit1}, {bit2})"); + return false; + } + } + } + + Message("Correct!"); + return true; + } + +} diff --git a/katas/content/superdense_coding/alice_sends_message/Placeholder.qs b/katas/content/superdense_coding/alice_sends_message/Placeholder.qs new file mode 100644 index 0000000000..8fc8ff7998 --- /dev/null +++ b/katas/content/superdense_coding/alice_sends_message/Placeholder.qs @@ -0,0 +1,9 @@ +namespace Kata { + operation EncodeMessageInQubit(qAlice : Qubit, message : (Bool, Bool)) : Unit { + // Get the bits from the message + let (bit1, bit2) = message; + + // Implement your solution here... + + } +} diff --git a/katas/content/superdense_coding/alice_sends_message/Solution.qs b/katas/content/superdense_coding/alice_sends_message/Solution.qs new file mode 100644 index 0000000000..6e29145a9a --- /dev/null +++ b/katas/content/superdense_coding/alice_sends_message/Solution.qs @@ -0,0 +1,13 @@ +namespace Kata { + operation EncodeMessageInQubit(qAlice : Qubit, message : (Bool, Bool)) : Unit { + let (bit1, bit2) = message; + + if bit2 { + X(qAlice); + } + + if bit1 { + Z(qAlice); + } + } +} diff --git a/katas/content/superdense_coding/alice_sends_message/Verification.qs b/katas/content/superdense_coding/alice_sends_message/Verification.qs new file mode 100644 index 0000000000..8daf795ed1 --- /dev/null +++ b/katas/content/superdense_coding/alice_sends_message/Verification.qs @@ -0,0 +1,7 @@ +namespace Kata.Verification { + @EntryPoint() + operation CheckSolution() : Bool { + return CheckProtocolWithFeedback(ComposeProtocol(Kata.EncodeMessageInQubit, DecodeMessageFromQubits_Reference, _)); + } + +} diff --git a/katas/content/superdense_coding/alice_sends_message/index.md b/katas/content/superdense_coding/alice_sends_message/index.md new file mode 100644 index 0000000000..cb3ea87eea --- /dev/null +++ b/katas/content/superdense_coding/alice_sends_message/index.md @@ -0,0 +1,16 @@ +**Inputs**: + +1. `qAlice` : Alice's part of the entangled pair of qubits. +2. `message`: Two classical bits represented by a tuple of of two `Bool` variables to represent `bit1` and `bit2` respectively. + +**Goal**: +Encode the message (two classical bits) by manipulating Alice's qubit. + +Superdense coding protocol changes the joint state of the two qubits to one of the following four states based on the value of message: + +- `(0, 0)`: $\ket{\Phi^{+}} = \frac{1}{\sqrt{2}} (\ket{00} + \ket{11})$ +- `(0, 1)`: $\ket{\Psi^{+}} = \frac{1}{\sqrt{2}} (\ket{01} + \ket{10})$ +- `(1, 0)`: $\ket{\Phi^{-}} = \frac{1}{\sqrt{2}} (\ket{00} - \ket{11})$ +- `(1, 1)`: $\ket{\Psi^{-}} = \frac{1}{\sqrt{2}} (\ket{01} - \ket{10})$ + +Note that your solution is tested as part of an end-to-end implementation of the protocol; the goal is to get the message transmitted correctly. diff --git a/katas/content/superdense_coding/alice_sends_message/solution.md b/katas/content/superdense_coding/alice_sends_message/solution.md new file mode 100644 index 0000000000..591c087db0 --- /dev/null +++ b/katas/content/superdense_coding/alice_sends_message/solution.md @@ -0,0 +1,35 @@ +Recall that we learnt how to prepare all Bell states in "Preparing Quantum States" kata. This is slightly advanced version of that task demonstrating how operations applied to one qubit of a Bell state allow us to transform it into any other Bell state. + +Superdense coding protocol uses the below Bell state we prepared in the previous task: + +$$\frac{1}{\sqrt{2}} \big(\ket{00} + \ket{11} \big)$$ + +We can transform it into every other Bell state according to the value of `message`: + +- `(0, 0)`: $\ket{\Phi^{+}} = \frac{1}{\sqrt{2}} (\ket{00} + \ket{11})$ +- `(0, 1)`: $\ket{\Psi^{+}} = \frac{1}{\sqrt{2}} (\ket{01} + \ket{10})$ +- `(1, 0)`: $\ket{\Phi^{-}} = \frac{1}{\sqrt{2}} (\ket{00} - \ket{11})$ +- `(1, 1)`: $\ket{\Psi^{-}} = \frac{1}{\sqrt{2}} (\ket{01} - \ket{10})$ + +Here is how we can perform this transformation: + +- If `bits == (0, 0)`, we do nothing - the prepared state is already $\ket{\Phi^{+}}$. + +- If `bits == (0, 1)`, we need to change the second qubit in both $\ket{00}$ and $\ket{11}$ terms. Observe that applying an $X$ gate to Alice's qubit does exactly that: + $$(X \otimes I) \ket{\Phi^{+}} = \frac{1}{\sqrt{2}} (\ket{10} + \ket{01})$$ + +- If `bits == (1, 0)`, we need to add a relative phase of $-1$ to the $\ket{11}$ term. Observe that applying $Z$ gate to Alice's qubit does exactly that: + $$(Z \otimes I) \ket{\Phi^{+}} = \frac{1}{\sqrt{2}} (\ket{00} - \ket{11})$$ + +- If `bits = (1, 1)`, we use the same logic to realize that we need to apply both the $Z$ and $X$ corrections to get $\ket{\Psi^{-}}$ state. + $$ (Z \otimes I) \cdot (X \otimes I) \ket{\Psi^{+}} = (Z \otimes I) \frac{1}{\sqrt{2}} (\ket{10} + \ket{01}) = \frac{1}{\sqrt{2}} (-\ket{10} + \ket{01}) = \frac{1}{\sqrt{2}} (\ket{01} - \ket{10}) $$ + +The final sequence of steps is as follows: + +1. Apply the $X$ gate to Alice's qubit if `bit2 == 1`. +2. Apply the $Z$ gate to Alice's qubit if `bit1 == 1`. + +@[solution]({ + "id": "superdense_coding__alice_sends_message_solution", + "codePath": "./Solution.qs" +}) diff --git a/katas/content/superdense_coding/bob_decodes_message/Placeholder.qs b/katas/content/superdense_coding/bob_decodes_message/Placeholder.qs new file mode 100644 index 0000000000..81efde8aa8 --- /dev/null +++ b/katas/content/superdense_coding/bob_decodes_message/Placeholder.qs @@ -0,0 +1,7 @@ +namespace Kata { + operation DecodeMessageFromQubits(qAlice : Qubit, qBob : Qubit) : (Bool, Bool) { + // Implement your solution here... + + return (false, false); + } +} diff --git a/katas/content/superdense_coding/bob_decodes_message/Solution.qs b/katas/content/superdense_coding/bob_decodes_message/Solution.qs new file mode 100644 index 0000000000..5076681df7 --- /dev/null +++ b/katas/content/superdense_coding/bob_decodes_message/Solution.qs @@ -0,0 +1,7 @@ +namespace Kata { + operation DecodeMessageFromQubits(qAlice : Qubit, qBob : Qubit) : (Bool, Bool) { + CNOT(qAlice, qBob); + H(qAlice); + return (MResetZ(qAlice) == One, MResetZ(qBob) == One); + } +} diff --git a/katas/content/superdense_coding/bob_decodes_message/Verification.qs b/katas/content/superdense_coding/bob_decodes_message/Verification.qs new file mode 100644 index 0000000000..0aa2dc148c --- /dev/null +++ b/katas/content/superdense_coding/bob_decodes_message/Verification.qs @@ -0,0 +1,7 @@ +namespace Kata.Verification { + @EntryPoint() + operation CheckSolution() : Bool { + return CheckProtocolWithFeedback(ComposeProtocol(EncodeMessageInQubit_Reference, Kata.DecodeMessageFromQubits, _)); + } +} + diff --git a/katas/content/superdense_coding/bob_decodes_message/index.md b/katas/content/superdense_coding/bob_decodes_message/index.md new file mode 100644 index 0000000000..bcca16aef5 --- /dev/null +++ b/katas/content/superdense_coding/bob_decodes_message/index.md @@ -0,0 +1,8 @@ +**Inputs:** + +1. `qAlice` : Qubit received from Alice. +2. `qBob` : Bob's part of the entangled pair. + +**Goal** : Decode the message using the qubit received from Alice. For this, retrieve two bits of classic data from the qubits and return a tuple of the form `(Bool, Bool)` to represent `message`. The state of the qubits in the end of the operation does not matter. + +Note that your solution is tested as part of an end-to-end implementation of the protocol; the goal is to get the message transmitted correctly. diff --git a/katas/content/superdense_coding/bob_decodes_message/solution.md b/katas/content/superdense_coding/bob_decodes_message/solution.md new file mode 100644 index 0000000000..dc54a05d6b --- /dev/null +++ b/katas/content/superdense_coding/bob_decodes_message/solution.md @@ -0,0 +1,22 @@ +Recall that Alice encoded qubits as follows: + +- `(0, 0)`: $\ket{\Phi^{+}} = \frac{1}{\sqrt{2}} (\ket{00} + \ket{11})$ +- `(0, 1)`: $\ket{\Psi^{+}} = \frac{1}{\sqrt{2}} (\ket{01} + \ket{10})$ +- `(1, 0)`: $\ket{\Phi^{-}} = \frac{1}{\sqrt{2}} (\ket{00} - \ket{11})$ +- `(1, 1)`: $\ket{\Psi^{-}} = \frac{1}{\sqrt{2}} (\ket{01} - \ket{10})$ + +To read out the encoded message, Bob needs to figure out which of the four Bell states he has. We can map the Bell states to basis states by applying a $CNOT$ gate with the first qubit as control and the second qubit as target, followed by an $H$ gate on the first qubit. (Notice that this is exactly what [`Adjoint`](https://learn.microsoft.com/azure/quantum/user-guide/language/expressions/functorapplication#adjoint-functor) of the state preparation operation in the first task does.) + +What is the outcome of this transformation, assuming each of the possible quantum states after the encoding step? + +- $\ket{\Phi^{+}} = \frac{1}{\sqrt{2}} (\ket{00} + \ket{11}) \rightarrow \ket{00}$ +- $\ket{\Psi^{+}} = \frac{1}{\sqrt{2}} (\ket{01} + \ket{10}) \rightarrow \ket{01}$ +- $\ket{\Phi^{-}} = \frac{1}{\sqrt{2}} (\ket{00} - \ket{11}) \rightarrow \ket{10}$ +- $\ket{\Psi^{-}} = \frac{1}{\sqrt{2}} (\ket{01} - \ket{10}) \rightarrow \ket{11}$ + +Hence, we can retrieve the encoded bits just by measuring the bits. + +@[solution]({ + "id": "superdense_coding__bob_decodes_message_solution", + "codePath": "./Solution.qs" +}) diff --git a/katas/content/superdense_coding/entangled_pair/Placeholder.qs b/katas/content/superdense_coding/entangled_pair/Placeholder.qs index 43078301ba..0ae507605d 100644 --- a/katas/content/superdense_coding/entangled_pair/Placeholder.qs +++ b/katas/content/superdense_coding/entangled_pair/Placeholder.qs @@ -1,5 +1,5 @@ namespace Kata { - operation CreateEntangledPair(qs : Qubit[]) : Unit is Adj { + operation CreateEntangledPair(qAlice : Qubit, qBob : Qubit) : Unit is Adj { // Implement your solution here... } diff --git a/katas/content/superdense_coding/entangled_pair/Solution.qs b/katas/content/superdense_coding/entangled_pair/Solution.qs index 46710f3b6b..268b8c46bf 100644 --- a/katas/content/superdense_coding/entangled_pair/Solution.qs +++ b/katas/content/superdense_coding/entangled_pair/Solution.qs @@ -1,6 +1,6 @@ namespace Kata { - operation CreateEntangledPair(qs : Qubit[]) : Unit is Adj { - H(qs[0]); - CNOT(qs[0], qs[1]); + operation CreateEntangledPair(qAlice : Qubit, qBob : Qubit) : Unit is Adj { + H(qAlice); + CNOT(qAlice, qBob); } } diff --git a/katas/content/superdense_coding/entangled_pair/Verification.qs b/katas/content/superdense_coding/entangled_pair/Verification.qs index 4a803b0697..9d9623320a 100644 --- a/katas/content/superdense_coding/entangled_pair/Verification.qs +++ b/katas/content/superdense_coding/entangled_pair/Verification.qs @@ -1,17 +1,16 @@ namespace Kata.Verification { open Microsoft.Quantum.Katas; - operation CreateEntangledPair(qs : Qubit[]) : Unit is Adj + Ctl { - H(qs[0]); - CNOT(qs[0], qs[1]); + operation CreateEntangledPair_Wrapper(qs : Qubit[]) : Unit is Adj { + Kata.CreateEntangledPair(qs[0], qs[1]); } @EntryPoint() operation CheckSolution() : Bool { return CheckOperationsEquivalenceOnZeroStateWithFeedback( - Kata.CreateEntangledPair, - CreateEntangledPair, + CreateEntangledPair_Wrapper, + CreateEntangledPairWrapper_Reference, 2 ); } diff --git a/katas/content/superdense_coding/entangled_pair/index.md b/katas/content/superdense_coding/entangled_pair/index.md index fe6a2fdbc5..71f356790a 100644 --- a/katas/content/superdense_coding/entangled_pair/index.md +++ b/katas/content/superdense_coding/entangled_pair/index.md @@ -1,3 +1,6 @@ -**Input:** Two qubits in the $|00\rangle$ state (stored in an array of length 2). +**Inputs:** -**Goal:** Prepare a Bell state $|\Phi^{+}\rangle = \frac{1}{\sqrt{2}} (|00\rangle + |11\rangle)$ on these qubits. +1. `qAlice` : Alice's qubit in $\ket{0}$ state. +2. `qBob` : Bob's qubit in $\ket{0}$ state. + +**Goal:** Prepare a Bell state $|\Phi^{+}\rangle = \frac{1}{\sqrt{2}} (\ket{00} + \ket{11})$ on these qubits. diff --git a/katas/content/superdense_coding/entangled_pair/solution.md b/katas/content/superdense_coding/entangled_pair/solution.md index ae9f63e57b..3815e355e4 100644 --- a/katas/content/superdense_coding/entangled_pair/solution.md +++ b/katas/content/superdense_coding/entangled_pair/solution.md @@ -2,7 +2,7 @@ Recall that this state is the Bell state that we have already seen in "Multi-Qub The solution takes two steps: -1. Apply Hadamard gate on first qubit to get the state: $\big(\frac{1}{\sqrt{2}}|0\rangle + \frac{1}{\sqrt{2}}|1\rangle\big) \otimes |0\rangle$. +1. Apply Hadamard gate on first qubit to get the state: $\big(\frac{1}{\sqrt{2}} \ket{0} + \frac{1}{\sqrt{2}} \ket{1} \big) \otimes \ket{0}$. 2. Apply a $CNOT$ gate with the first qubit as the control and the second qubit as the target. @[solution]({ diff --git a/katas/content/superdense_coding/index.md b/katas/content/superdense_coding/index.md index bf23fda761..f51dbd0c96 100644 --- a/katas/content/superdense_coding/index.md +++ b/katas/content/superdense_coding/index.md @@ -1,7 +1,7 @@ -# Superdense Coding Kata +# Superdense Coding @[section]({ - "id": "./superdense_coding__overview", + "id": "superdense_coding__overview", "title": "Overview" }) @@ -18,10 +18,45 @@ We split the superdense coding protocol into several steps: - Finally, we compose those steps into the complete superdense coding protocol. @[exercise]({ - "id": "./superdense_coding__entangled_pair", + "id": "superdense_coding__entangled_pair", "title": "Entangled Pair", "path": "entangled_pair", "qsDependencies": [ - "../KatasLibrary.qs" + "../KatasLibrary.qs", + "./Common.qs" ] }) + +@[exercise]({ + "id": "superdense_coding__alice_sends_message", + "title": "Alice's Task: Encode Message", + "path": "alice_sends_message", + "qsDependencies": [ + "./Common.qs" + ] +}) + +@[exercise]({ + "id": "superdense_coding__bob_decodes_message", + "title": "Bob's Task: Decode Message", + "path": "bob_decodes_message", + "qsDependencies": [ + "./Common.qs" + ] +}) + +@[exercise]({ + "id": "superdense_coding__protocol_e2e", + "title": "Superdense Coding Protocol: End to End", + "path": "protocol_e2e", + "qsDependencies": [ + "./Common.qs" + ] +}) + +@[section]({ + "id": "superdense_coding__conclusion", + "title": "Conclusion" +}) + +Congratulations! In this kata you learned to implement superdense coding protocol. diff --git a/katas/content/superdense_coding/protocol_e2e/Placeholder.qs b/katas/content/superdense_coding/protocol_e2e/Placeholder.qs new file mode 100644 index 0000000000..25da4aaac9 --- /dev/null +++ b/katas/content/superdense_coding/protocol_e2e/Placeholder.qs @@ -0,0 +1,31 @@ +namespace Kata { + operation SuperdenseCodingProtocol(message : (Bool, Bool)) : (Bool, Bool) { + // Implement your solution here... + + return (false, false); + } + + // You might find these helper operations from earlier tasks useful. + operation CreateEntangledPair(qAlice : Qubit, qBob : Qubit) : Unit is Adj { + H(qAlice); + CNOT(qAlice, qBob); + } + + operation EncodeMessageInQubit(qAlice : Qubit, message : (Bool, Bool)) : Unit { + let (bit1, bit2) = message; + + if bit2 { + X(qAlice); + } + + if bit1 { + Z(qAlice); + } + } + + operation DecodeMessageFromQubits(qAlice : Qubit, qBob : Qubit) : (Bool, Bool) { + CNOT(qAlice, qBob); + H(qAlice); + return (MResetZ(qAlice) == One, MResetZ(qBob) == One); + } +} diff --git a/katas/content/superdense_coding/protocol_e2e/Solution.qs b/katas/content/superdense_coding/protocol_e2e/Solution.qs new file mode 100644 index 0000000000..d669ebfe96 --- /dev/null +++ b/katas/content/superdense_coding/protocol_e2e/Solution.qs @@ -0,0 +1,35 @@ +namespace Kata { + + operation SuperdenseCodingProtocol(message : (Bool, Bool)) : (Bool, Bool) { + use (qAlice, qBob) = (Qubit(), Qubit()); + + CreateEntangledPair(qAlice, qBob); + + EncodeMessageInQubit(qAlice, message); + + return DecodeMessageFromQubits(qAlice, qBob); + } + + operation CreateEntangledPair(qAlice : Qubit, qBob : Qubit) : Unit is Adj { + H(qAlice); + CNOT(qAlice, qBob); + } + + operation EncodeMessageInQubit(qAlice : Qubit, message : (Bool, Bool)) : Unit { + let (bit1, bit2) = message; + + if bit2 { + X(qAlice); + } + + if bit1 { + Z(qAlice); + } + } + + operation DecodeMessageFromQubits(qAlice : Qubit, qBob : Qubit) : (Bool, Bool) { + CNOT(qAlice, qBob); + H(qAlice); + return (MResetZ(qAlice) == One, MResetZ(qBob) == One); + } +} diff --git a/katas/content/superdense_coding/protocol_e2e/Verification.qs b/katas/content/superdense_coding/protocol_e2e/Verification.qs new file mode 100644 index 0000000000..2bc9dce965 --- /dev/null +++ b/katas/content/superdense_coding/protocol_e2e/Verification.qs @@ -0,0 +1,6 @@ +namespace Kata.Verification { + @EntryPoint() + operation CheckSolution() : Bool { + return CheckProtocolWithFeedback(Kata.SuperdenseCodingProtocol); + } +} diff --git a/katas/content/superdense_coding/protocol_e2e/index.md b/katas/content/superdense_coding/protocol_e2e/index.md new file mode 100644 index 0000000000..b9394b2e58 --- /dev/null +++ b/katas/content/superdense_coding/protocol_e2e/index.md @@ -0,0 +1,9 @@ +**Input:** Two classical bits to be transmitted. + +**Goal:** +Put together the steps performed in previous tasks to implement the full superdense coding protocol: + +- Prepare a Bell state. +- Encode the two classical bits in the state of the qubits by applying quantum gates to one qubit of the Bell state, +- Decode the two classical bits from the resulting state. +- Return the result of decoding. diff --git a/katas/content/superdense_coding/protocol_e2e/solution.md b/katas/content/superdense_coding/protocol_e2e/solution.md new file mode 100644 index 0000000000..8bda374dd7 --- /dev/null +++ b/katas/content/superdense_coding/protocol_e2e/solution.md @@ -0,0 +1,11 @@ +We can put together the operations defined in the previous tasks to run superdense coding protocol end-to-end. + +- Prepare a Bell state. +- Encode the two classical bits in the state of the qubits by applying quantum gates to one qubit of the Bell state, +- Decode the two classical bits from the resulting state. +- Return the result of decoding. + +@[solution]({ + "id": "superdense_coding__protocol_e2e_solution", + "codePath": "./Solution.qs" +})