Skip to content

Commit

Permalink
Migrate QFT kata, part 1 (#1809)
Browse files Browse the repository at this point in the history
Includes 6 tasks from classic QFT kata (except register reverse and
inverse QFT) and a new section that discusses equivalence of two QFT
notations used in the definition and the implementation.

---------

Co-authored-by: César Zaragoza Cortés <[email protected]>
  • Loading branch information
tcNickolas and cesarzc authored Aug 1, 2024
1 parent 5655f6c commit 76bbdb7
Show file tree
Hide file tree
Showing 32 changed files with 611 additions and 0 deletions.
6 changes: 6 additions & 0 deletions katas/content/qft/binary_fraction_classical/Placeholder.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Kata {
operation BinaryFractionClassical(q : Qubit, j : Bool[]) : Unit is Adj + Ctl {
// Implement your solution here...

}
}
9 changes: 9 additions & 0 deletions katas/content/qft/binary_fraction_classical/SolutionA.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Kata {
operation BinaryFractionClassical(q : Qubit, j : Bool[]) : Unit is Adj + Ctl {
for ind in 0 .. Length(j) - 1 {
if j[ind] {
R1Frac(2, ind + 1, q);
}
}
}
}
11 changes: 11 additions & 0 deletions katas/content/qft/binary_fraction_classical/SolutionB.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Kata {
open Microsoft.Quantum.Arrays;
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Math;

operation BinaryFractionClassical(q : Qubit, j : Bool[]) : Unit is Adj + Ctl {
let n = Length(j);
let jIntBE = BoolArrayAsInt(Reversed(j));
R1(2.0 * PI() * IntAsDouble(jIntBE) / IntAsDouble(1 <<< n), q);
}
}
32 changes: 32 additions & 0 deletions katas/content/qft/binary_fraction_classical/Verification.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Kata.Verification {
open Microsoft.Quantum.Arrays;
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Katas;
open Microsoft.Quantum.Math;

operation BinaryFractionClassical_Alternative (q : Qubit, j : Bool[]) : Unit is Adj+Ctl {
// Convert the number to an integer and apply a single R1 rotation
R1(2.0 * PI() * IntAsDouble(BoolArrayAsInt(Reversed(j))) / IntAsDouble(1 <<< Length(j)), q);
}

@EntryPoint()
operation CheckSolution() : Bool {
for n in 1 .. 5 {
for exp in 0 .. (1 <<< n) - 1 {
let j = Reversed(IntAsBoolArray(exp, n));
let solution = qs => Kata.BinaryFractionClassical(qs[0], j);
let reference = qs => BinaryFractionClassical_Alternative(qs[0], j);
if not CheckOperationsAreEqualStrict(1, solution, reference) {
Message($"Incorrect for j = {j}.");
Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " +
"is expected to have.");
ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference);
return false;
}
}
}

Message("Correct!");
true
}
}
9 changes: 9 additions & 0 deletions katas/content/qft/binary_fraction_classical/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
**Inputs**:

1. A qubit in state $\ket{\psi} = \alpha \ket{0} + \beta \ket{1}$.
2. An array of $n$ bits $[j_1, j_2, ..., j_n]$, stored as `Bool[]`.

**Goal**:
Change the state of the qubit to $\alpha \ket{0} + \beta \cdot e^{2\pi i \cdot 0.j_1 j_2 ... j_n} \ket{1}$, where $0.j_1 j_2 ... j_n$ is a binary fraction in big endian notation, similar to decimal fractions:

$$0.j_1 j_2 ... j_n = j_1 \cdot \frac{1}{2^1} + j_2 \cdot \frac{1}{2^2} + ... + j_n \cdot \frac{1}{2^n}$$
56 changes: 56 additions & 0 deletions katas/content/qft/binary_fraction_classical/solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
Since you can express the exponent of a sum as a product of exponents ($e^{a+b} = e^a \cdot e^b$), you can implement the required transformation as a sequence of rotation gates from the previous task with increasing $k$.

Each of the individual rotations will use the bit $j_k$ to decide whether to apply the rotation (you only want to apply the rotation if $j_k$ is true), and the index of that bit $k$ to define the rotation angle.
The gate applied for each $k$ will be:

$$U_k = \begin{cases}
I, j_k=0 \\
\textrm{R1Frac}(2, k), j_k=1
\end{cases}$$

Recall that

$$ \textrm{R1Frac}(2, k)( \alpha \ket{0} + \beta \ket{1}) = \alpha \ket{0} + \beta \cdot e^{2\pi i/2^{k}} \ket{1} = \alpha \ket{0} + \beta \cdot e^{2\pi i \cdot 0. \underset{k-1}{\underbrace{0\dots0}} 1} \ket{1}$$

This means that the overall effect of the gate $U_k$ for each $k$ is:

$$U_k (\alpha \ket{0} + \beta \ket{1}) = \alpha \ket{0} + \beta \cdot e^{2\pi i \cdot 0. \underset{k-1}{\underbrace{0\dots0}} j_k} \ket{1}$$

As you iterate over $k$, the resulting state will get closer and closer to the required one:

<table>
<tr>
<th>$k$</th>
<th>State after step $k$</th>
</tr>
<tr>
<td>$1$</td>
<td>$$\alpha \ket{0} + \beta \cdot e^{2\pi i \cdot 0.j_1} \ket{1}$$</td>
</tr>
<tr>
<td>$2$</td>
<td>$$\alpha \ket{0} + \beta \cdot e^{2\pi i \cdot 0.j_1j_2} \ket{1}$$</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
</tr>
<tr>
<td>$n$</td>
<td>$$\alpha \ket{0} + \beta \cdot e^{2\pi i \cdot 0.j_1j_2 \dots j_n}\ket{1}$$</td>
</tr>
</table>

@[solution]({
"id": "qft__binary_fraction_classical_solution_a",
"codePath": "./SolutionA.qs"
})

Alternatively, you can do this in a single rotation using the $R1$ gate if you convert the array $j$ into a rotation angle. You'll need the angle $2\pi \cdot 0.j_1j_2 \dots j_n$, and this fraction can be calculated by converting the bit string into an integer $j$ (using big endian notation) and dividing it by $2^n$.

This solution is better when considered on its own, since it involves doing only a single rotation rather than a series of them. However, it will not be as helpful as the first one once you get to the next task!

@[solution]({
"id": "qft__binary_fraction_classical_solution_b",
"codePath": "./SolutionB.qs"
})
6 changes: 6 additions & 0 deletions katas/content/qft/binary_fraction_inplace/Placeholder.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Kata {
operation BinaryFractionQuantumInPlace(j : Qubit[]) : Unit is Adj + Ctl {
// Implement your solution here...

}
}
8 changes: 8 additions & 0 deletions katas/content/qft/binary_fraction_inplace/Solution.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Kata {
operation BinaryFractionQuantumInPlace(j : Qubit[]) : Unit is Adj + Ctl {
H(j[0]);
for ind in 1 .. Length(j) - 1 {
Controlled R1Frac([j[ind]], (2, ind + 1, j[0]));
}
}
}
26 changes: 26 additions & 0 deletions katas/content/qft/binary_fraction_inplace/Verification.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Kata.Verification {
open Microsoft.Quantum.Arrays;
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Katas;
open Microsoft.Quantum.Math;

operation BinaryFractionQuantumInPlace_Reference(j : Qubit[]) : Unit is Adj + Ctl {
H(j[0]);
for ind in 1 .. Length(j) - 1 {
Controlled R1Frac([j[ind]], (2, ind + 1, j[0]));
}
}

@EntryPoint()
operation CheckSolution() : Bool {
for n in 1 .. 5 {
if not CheckOperationsAreEqualStrict(n, Kata.BinaryFractionQuantumInPlace, BinaryFractionQuantumInPlace_Reference) {
Message($"Incorrect for n = {n}.");
return false;
}
}

Message("Correct!");
true
}
}
11 changes: 11 additions & 0 deletions katas/content/qft/binary_fraction_inplace/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
**Input**:
A register of $n$ qubits in state $\ket{j_1 j_2 ... j_n}$.

**Goal**:
Change the state of the register from $\ket{j_1} \otimes \ket{j_2 ... j_n}$ to $\frac1{\sqrt2}(\ket{0} + e^{2\pi i \cdot 0.j_1 j_2 ... j_n} \ket{1}) \otimes \ket{j_2 ... j_n}$.

<details>
<summary><b>Need a hint?</b></summary>

This task is very similar to the previous task, but the digit $j_1$ has to be encoded in-place. You can do this using the first task of the kata, "Single-Qubit QFT".
</details>
14 changes: 14 additions & 0 deletions katas/content/qft/binary_fraction_inplace/solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
First, let's recall the first task of the kata: a Hadamard gate applied to a single qubit, $H\ket{j_1}$, will give either
$\frac1{\sqrt2}(\ket{0} + \ket{1})$ or $\frac{1}{\sqrt{2}}(\ket{0} - \ket{1})$ depending on the state of $\ket{j_1}$. This operation can also be written as
$$H\ket{j_1} = \frac1{\sqrt2} \big(\ket{0} + e^{2\pi i \cdot \frac{j_1}{2}}\ket{1} \big)= \frac1{\sqrt2} \big(\ket{0} + e^{2\pi i \cdot 0.j_1}\ket{1} \big)$$

So, if the starting register state is $\ket{j_1 j_2 ... j_n}$, applying a Hadamard gate to the first qubit will result in:

$$\big(H_1\otimes I_{n-1} \big) \big(\ket{j_1} \otimes \ket{j_2 ... j_n} \big)= \frac1{\sqrt2} \big(\ket{0} + e^{2\pi i \cdot 0.j_1}\ket{1} \big) \otimes \ket{j_2 ... j_n} $$

After this, we can repeat the loop we used in the previous task for qubits $\ket{j_2 ... j_n}$ with the first qubit as the target to adjust the remaining phase terms via the controlled $\textrm{R1Frac}$ gate.

@[solution]({
"id": "qft__binary_fraction_inplace_solution",
"codePath": "./Solution.qs"
})
6 changes: 6 additions & 0 deletions katas/content/qft/binary_fraction_quantum/Placeholder.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Kata {
operation BinaryFractionQuantum(q : Qubit, j : Qubit[]) : Unit is Adj + Ctl {
// Implement your solution here...

}
}
7 changes: 7 additions & 0 deletions katas/content/qft/binary_fraction_quantum/Solution.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Kata {
operation BinaryFractionQuantum(q : Qubit, j : Qubit[]) : Unit is Adj + Ctl {
for ind in 0 .. Length(j) - 1 {
Controlled R1Frac([j[ind]], (2, ind + 1, q));
}
}
}
27 changes: 27 additions & 0 deletions katas/content/qft/binary_fraction_quantum/Verification.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Kata.Verification {
open Microsoft.Quantum.Arrays;
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Katas;
open Microsoft.Quantum.Math;

operation BinaryFractionQuantum_Reference(q : Qubit, j : Qubit[]) : Unit is Adj + Ctl {
for ind in 0 .. Length(j) - 1 {
Controlled R1Frac([j[ind]], (2, ind + 1, q));
}
}

@EntryPoint()
operation CheckSolution() : Bool {
for n in 1 .. 5 {
let solution = qs => Kata.BinaryFractionQuantum(qs[0], qs[1 ...]);
let reference = qs => BinaryFractionQuantum_Reference(qs[0], qs[1 ...]);
if not CheckOperationsAreEqualStrict(n + 1, solution, reference) {
Message($"Incorrect for n = {n}.");
return false;
}
}

Message("Correct!");
true
}
}
10 changes: 10 additions & 0 deletions katas/content/qft/binary_fraction_quantum/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
**Inputs**:

1. A qubit in state $\ket{\psi} = \alpha \ket{0} + \beta \ket{1}$.
2. An array of $n$ qubits $j$ in state $\ket{j_1 j_2 ... j_n}$.

**Goal**:
Change the state of the input qubits from $(\alpha \ket{0} + \beta \ket{1}) \otimes \ket{j_1 j_2 ... j_n}$ to $(\alpha \ket{0} + \beta \cdot e^{2\pi i \cdot 0.j_1 j_2 ... j_n} \ket{1}) \otimes \ket{j_1 j_2 ... j_n}$, where $0.j_1 j_2 ... j_n$ is a binary fraction corresponding to the basis state $j_1 j_2 ... j_n$ of the qubit array $j$.

> The qubit array can be in superposition as well;
the behavior of the transformation in this case is defined by its behavior on the basis states and the linearity of unitary transformations.
8 changes: 8 additions & 0 deletions katas/content/qft/binary_fraction_quantum/solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
From the goal of the exercise, you can see that the register $j$ has to remain unchanged, while the first input qubit should acquire a phase that depends on the value of the qubits in the register $j$.

Since $j$ is a quantum register and can be in a superposition of basis states, you cannot just measure the register and then apply the operation from the previous task using measurement results as the second argument. Instead, you have to convert the solution to the previous task from using $\textrm{R1Frac}$ gates with classical conditions to using them as controlled operations, with the qubits of the register $j$ as quantum conditions. You can do this using the `Controlled` functor.

@[solution]({
"id": "qft__binary_fraction_quantum_solution",
"codePath": "./Solution.qs"
})
Loading

0 comments on commit 76bbdb7

Please sign in to comment.