Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finish migration of QPE kata and publish it #1837

Merged
merged 5 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions katas/content/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@
"solving_sat",
"solving_graph_coloring",
"qft",
"phase_estimation",
"qec_shor"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace Kata {
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Diagnostics;
open Microsoft.Quantum.Math;

@EntryPoint()
operation QuantumPhaseEstimationDemo() : Unit {
// Experiment with the parameters to explore algorithm behavior for different eigenphases!
// Use R1Frac(k, n, _) for eigenvalue exp(2π𝑖 k/2^(n+1)), eigenphase k/2^(n+1)
// or R1(theta, _) for eigenvalue exp(2π𝑖 theta/2), eigenphase theta/2
// Here are some convenient unitaries and their eigenphases
// R1Frac(1, 0, _) | 0.5
// R1Frac(1, 1, _) | 0.25
// R1Frac(1, 2, _) | 0.125
// R1Frac(1, 3, _) | 0.0625
let U = R1Frac(1, 3, _);
let P = X; // |1⟩ basis state is convenient to experiment with R1 and R1Frac gates
let n = 3;
mutable counts = [0, size = 2 ^ n];
for _ in 1 .. 100 {
let res = PhaseEstimation(U, P, n);
set counts w/= res <- counts[res] + 1;
}
for i in 0 .. 2 ^ n - 1 {
if counts[i] > 0 {
Message($"Eigenphase {IntAsDouble(i) / IntAsDouble(2 ^ n)} - {counts[i]}%");
}
}
}

operation PhaseEstimation(
U : Qubit => Unit is Ctl,
P : Qubit => Unit,
n : Int
) : Int {
use (phaseRegister, eigenstate) = (Qubit[n], Qubit());
P(eigenstate);
ApplyToEach(H, phaseRegister);
for k in 0 .. n - 1 {
for _ in 1 .. 1 <<< k {
Controlled U([phaseRegister[k]], eigenstate);
}
}
SwapReverseRegister(phaseRegister);
Adjoint ApplyQFT(phaseRegister);

Reset(eigenstate);
return MeasureInteger(phaseRegister);
}
}
11 changes: 11 additions & 0 deletions katas/content/phase_estimation/implement_qpe/Placeholder.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Kata {
operation PhaseEstimation(
U : Qubit => Unit is Ctl,
P : Qubit => Unit,
n : Int
) : Int {
// Implement your solution here...

return 0;
}
}
21 changes: 21 additions & 0 deletions katas/content/phase_estimation/implement_qpe/Solution.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Kata {
operation PhaseEstimation(
U : Qubit => Unit is Ctl,
P : Qubit => Unit,
n : Int
) : Int {
use (phaseRegister, eigenstate) = (Qubit[n], Qubit());
P(eigenstate);
ApplyToEach(H, phaseRegister);
for k in 0 .. n - 1 {
for _ in 1 .. 1 <<< k {
Controlled U([phaseRegister[k]], eigenstate);
}
tcNickolas marked this conversation as resolved.
Show resolved Hide resolved
}
SwapReverseRegister(phaseRegister);
Adjoint ApplyQFT(phaseRegister);

Reset(eigenstate);
return MeasureInteger(phaseRegister);
}
}
32 changes: 32 additions & 0 deletions katas/content/phase_estimation/implement_qpe/Verification.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Kata.Verification {
open Microsoft.Quantum.Unstable.StatePreparation;

@EntryPoint()
operation CheckSolution() : Bool {
let tests = [
(Z, I, 1, 0, "Z, |0⟩"),
(Z, X, 1, 1, "Z, |1⟩"),
(X, H, 1, 0, "X, |+⟩"),
(X, q => PreparePureStateD([1., -1.], [q]), 1, 1, "X, |-⟩"),
(S, I, 2, 0, "S, |0⟩"),
(S, X, 2, 1, "S, |1⟩"),
(Z, X, 2, 2, "Z, |1⟩"), // Higher precision than necessary
(T, I, 3, 0, "T, |0⟩"),
(T, X, 3, 1, "T, |1⟩"),
(S, X, 3, 2, "S, |1⟩"), // Higher precision than necessary
(Z, X, 3, 4, "Z, |1⟩"), // Higher precision than necessary
];
for (U, P, n, expected, msg) in tests {
for _ in 1 .. 10 { // Repeat several times to catch probabilistic failures
let actual = Kata.PhaseEstimation(U, P, n);
if actual != expected {
Message($"Incorrect eigenphase for (U, |ψ⟩, n) = ({msg}, {n}): expected {expected}, got {actual}");
return false;
}
}
}

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

1. A single-qubit unitary $U$. The unitary will have its controlled variant defined.
2. A single-qubit state $\ket{\psi}$, given as a unitary $P$ that prepares it from the $\ket{0}$ state. In other words, the result of applying the unitary $P$ to the state $\ket{0}$ is the $\ket{\psi}$ state:
$$P\ket{0} = \ket{\psi}$$
3. A positive integer $n$.

**Output:** Return the eigenphase $\theta$ which corresponds to the eigenstate $\ket{\psi}$, multiplied by $2^n$. You are guaranteed that this eigenvalue has at most $n$ digits of binary precision, so multiplying it by $2^n$ yields an integer.
12 changes: 12 additions & 0 deletions katas/content/phase_estimation/implement_qpe/solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
To solve this exercise, you need to follow the QPE algorithm as described in this lesson.

1. Allocate two qubit registers, $n$-qubit `phaseRegister` and single-qubit `eigenstate` (this one can be just an individual qubit rather than a qubit array for simplicity).
2. Prepare the initial state of the algorithm: `eigenstate` in the state $\ket{\psi}$ using the unitary $P$ and `phaseRegister` in the state that is an even superposition of all basis states using Hadamard gates.
3. Apply the controlled $U$ gates using two `for` loops. The outer loop will iterate over the qubits of `phaseRegister`, from the first qubit storing the least significant digit to the last one storing the most significant one. The inner loop will apply controlled $U$ $2^k$ times, where $k$ is the variable used as the outer loop counter.
4. Apply the adjoint QFT. Here, you can use the library operation `ApplyQFT`, keeping in mind that it applies only the rotations part of the quantum Fourier transform. To implement the complete transform using this operation, you need to reverse the order of qubits in the register after applying `ApplyQFT`. Since here you need to use adjoint QFT, you need to reverse the order of qubits before calling `Adjoint ApplyQFT`.
5. Measure the qubits in `phaseRegister` and convert the result to an integer. `MeasureInteger` does exactly that for a register in little endian. You also need to reset the qubit used as the eigenstate before releasing it.

@[solution]({
"id": "phase_estimation__implement_qpe_solution",
"codePath": "Solution.qs"
})
142 changes: 138 additions & 4 deletions katas/content/phase_estimation/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,151 @@ $$\theta = 0.\theta_1 \theta_2... \theta_n = \frac{\theta_1}{2^1}+ \frac{\theta_

Let's consider a simplified variant of the phase estimation problem, in which you are guaranteed that the phase $\theta$ has exactly one binary digit, that is, it's either $0$ or $\frac12$.

- exercise: solve for one bit eigenphase
@[exercise]({
"id": "phase_estimation__one_bit_eigenphase",
"title": "One-Bit Phase Estimation",
"path": "./one_bit_eigenphase/"
})


@[section]({
"id": "phase_estimation__qpe",
"title": "Quantum Phase Estimation Algorithm"
})

- theory
- exercise: task 1.4 to implement QPE
- demo of end-to-end probabilistic behavior in case of lower precision (use R1 gate)
The quantum phase estimation algorithm is the most common algorithm used for estimating the eigenphase that corresponds to the given unitary-eigenvector pair.
It relies on the quantum Fourier transform, and allows you to estimate $n$ binary digits of the eigenphase $\theta$.
Let's see how this algorithm works.

### Inputs

1. A single-qubit unitary $U$ that acts on $m$ qubits.
2. An $m$-qubit state $\ket{\psi}$ - an eigenvector of $U$.
3. An integer $n$ that specifies the desired binary precision of the eigenphase estimate.

### The starting state

The algorithm acts on $n+m$ qubits, split in two registers.
- The first $n$ qubits start in the state $\ket{0...0} = \ket{0}^{\otimes N}$. These qubits are used to get the eigenvalue.
- The last $m$ qubits start in the state $\ket{\psi}$ - the eigenvector of $U$.

### Step 1. Apply the Hadamard transform to each qubit of the first register

This step prepares the first qubit in an even superposition of all $n$-qubit basis states, or, equivalently, in an even superposition of all integers from $\ket{0}$ to $\ket{2^n-1}$.

The state of the system will end up being

$$\frac1{\sqrt{2^n}}\sum_{k=0}^{2^n-1} \ket{k} \otimes \ket{\psi}$$


### Step 2. Apply the controlled $U$ ladder

On this step, the algorithm applies a series of controlled $U$ gates, with different qubits of the first register as controls and the second register as the target.

Let's assume that the first register stores integers in little-endian notation: the first qubit corresponds to the least-significant bit, and the last qubit - to the most-significant bit. Then, the sequence of gates applied on this step looks as follows:

1. Controlled $U$ gate with the first qubit as control.
2. Controlled $U^2$ gate with the second qubit as control.
3. Controlled $U^4$ gate with the third qubit as control.
4. And so on, ending with controlled $U^{2^{n-1}}$ gate with qubit $n$ as control.

What does this sequence of gates do?

Let's consider the scenario in which the first register is in the basis state $\ket{k} = \ket{k_0} \otimes \ket{k_1} \otimes ... \otimes \ket{k_{n-1}}$, where $k$ is an integer with $n$ bits in its binary notation:
$$k = 2^0k_0 + 2^1 k_1 + 2^2k_2 + ... + 2^{n-1}k_{n-1}$$

In this case, this sequence of gates will apply the unitary $U^k$ to the second register! Indeed,

1. Controlled $U$ gate with the first qubit as control will apply the $U$ gate if $k_0 = 1$, and do nothing if $k_0 = 0$.
2. Controlled $U^2$ gate with the second qubit as control will apply the $U^2$ gate if $k_1 = 1$, and do nothing if $k_1 = 0$.
3. Controlled $U^4$ gate with the third qubit as control will apply the $U^4$ gate if $k_2 = 1$, and do nothing if $k_2 = 0$.
4. And so on.

You can see that in the end all the powers of $U$ applied to the second register add up to exactly the number $k$ written in the first register.

If the first register is in a superposition of states, as is the case for this algorithm, you can write the effect of controlled gates on the state of the system using linearity of unitary transformations:

$$\frac1{\sqrt{2^n}}\sum_{k=0}^{2^n-1} \ket{k} \otimes \ket{\psi}\rightarrow \frac1{\sqrt{2^n}}\sum_{k=0}^{2^n-1} \ket{k} \otimes U^k\ket{\psi}$$

Now, recall that $\ket{\psi}$ is an eigenvector of $U$:
$$U\ket{\psi} = e^{2 \pi i \theta} \ket{\psi}$$

This means that
$$U^k\ket{\psi} = (e^{2 \pi i \theta})^k \ket{\psi} = e^{2 \pi i \theta \cdot k} \ket{\psi}$$

You can use this to write the following expression for the state of the system after this step:

$$\frac1{\sqrt{2^n}}\sum_{k=0}^{2^n-1} e^{2 \pi i \theta \cdot k} \ket{k} \otimes \ket{\psi}$$

Notice that the first and second registers end up not being entangled with each other.


### Step 3. Apply the inverse QFT

Now, recall that the quantum Fourier transform that acts on a basis state $\ket{j}$ that represents an $n$-bit integer $j$ performs the following transformation:

$$\ket{j} \rightarrow \frac1{\sqrt{2^n}}\sum_{k=0}^{2^n-1} e^{2\pi i jk/2^n} \ket{k} = \frac1{\sqrt{2^n}}\sum_{k=0}^{2^n-1} e^{2\pi i (j/2^n) \cdot k} \ket{k}$$

If you compare this expression with the state of the first register at the end of the previous step, you'll notice that they match exactly, with $\theta = j / {2^n}$.

This means that you can use the inverse Fourier transform (the adjoint of the Fourier transform) to convert the state of the first register to a binary notation of the integer that stores the first $n$ bits of $\theta$ written as a binary fraction.
If $\theta = 0.\theta_1 \theta_2... \theta_n$, applying the inverse QFT to the first register will yield a state

$$\ket{j} = \ket{\theta_n} \otimes ... \otimes \ket{\theta_1}$$

(Remember that the previous step assumed that the first register stores the integers in little-endian notation, so you have to use the same notation on this step.)

### Step 4. Measure the first register

On the last step, you measure the first register, convert the measurement results to an integer, and divide it by $2^n$ to get the eigenphase $\theta$.

@[exercise]({
"id": "phase_estimation__implement_qpe",
"title": "Implement QPE Algorithm",
"path": "./implement_qpe/"
})


@[section]({
"id": "phase_estimation__practicality",
"title": "QPE Algorithm: Practical Considerations"
})

Now that you've learned to implement the QPE algorithm, let's discuss several practical aspects of its applications.

### Applying QPE to a superposition of eigenvectors

First of all, the algorithm inputs include an eigenvector of the unitary for which you want to estimate the eigenphase. In some applications, the eigenvector is either unknown or hard to prepare accurately. Is there a workaround for these scenarios?

To answer this, let's see what happens if you run the phase estimation algorithm for a state $\ket{\phi}$ that is not an eigenstate of the unitary $U$.

For any unitary $U$, you can find a basis that consists of its eigenvectors. This means that any state $\ket{\phi}$ can be represented as a superposition of eigenvectors $\ket{\psi_k}$:
$$\ket{\phi} = \sum_k a_k\ket{\psi_k}$$

You can notice that all the steps of the QPE algorithm until the final measurement are linear transformations. This means that you can apply them to each term in the superposition independently.

The state of the system right before the final measurement will look as follows:

$$\sum_k a_k \ket{j_k} \ket{\psi_k}$$

Here $j_k$ is the integer representation of the $n$ binary digits of the eigenphase that corresponds to the eigenvector $\ket{\psi_k}$.

Now, when you measure the first register, this measurement will cause the collapse of the superposition: you'll get one of the values $j_k$ that describes one of the eigenphases, and the second register will collapse to the matching eigenvector.

This means that if you're looking for *any* eigenphase of the unitary, you can just run the QPE algorithm with any state in place of the eigenvector. And if you know the eigenvector you want to use but it's tricky to prepare precisely, you can prepare it approximately and run the QPE algorithm on this state to get the eigenphase associated with it with high probability.


### Eigenphases that are not $n$-bit binary fractions

The algorithm derivation in the previous lesson assumed that the eigenphase $\theta$ can be written down with exactly $n$ binary bits of precision. However, this is not always the case. What happens if the eigenphase of the given eigenvector can't be written down using exactly $n$ binary bits?

It turns out that the QPE algorithm running with $n$ bits of precision is deterministic only for eigenphases that can be represented precisely as $n$-bit binary fractions. Running it for any eigenphases that don't have such a representation yields probabilistic results. The outcomes that are the closest $n$-bit fractions to the true eigenphase are produced with the highest probability, but it's also possible to get outcomes that are pretty far away from the true eigenphase.

The following demo allows you to play with the QPE algorithm for different eigenphases and to observe its behavior in different scenarios.
For example, you can see that running phase estimation for the gate `R1Frac(1, 3, _)` which has eigenphase $\frac1{16} = 0.0625$
with $n = 3$ bits of precision yields non-deterministic results, with the two most frequent outcomes being $0$ and $\frac18 = 0.125$.

@[example]({"id": "phase_estimation__e2edemo", "codePath": "./examples/QuantumPhaseEstimationDemo.qs"})


@[section]({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Kata {
operation OneBitPhaseEstimation(U : Qubit => Unit is Ctl, P : Qubit => Unit) : Int {
// Implement your solution here...

return 0;
}
}
11 changes: 11 additions & 0 deletions katas/content/phase_estimation/one_bit_eigenphase/Solution.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Kata {
operation OneBitPhaseEstimation(U : Qubit => Unit is Ctl, P : Qubit => Unit) : Int {
use (control, eigenstate) = (Qubit(), Qubit());
H(control);
tcNickolas marked this conversation as resolved.
Show resolved Hide resolved
P(eigenstate);
Controlled U([control], eigenstate);
Reset(eigenstate);

return MResetX(control) == Zero ? 1 | -1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Kata.Verification {
open Microsoft.Quantum.Unstable.StatePreparation;

@EntryPoint()
operation CheckSolution() : Bool {
let eigenvectors = [
(Z, I, 1, "Z, |0⟩"),
(Z, X, -1, "Z, |1⟩"),
(S, I, 1, "S, |0⟩"),
(X, H, 1, "X, |+⟩"),
(X, q => PreparePureStateD([1., -1.], [q]), -1, "X, |-⟩")];
for (U, P, expected, msg) in eigenvectors {
let actual = Kata.OneBitPhaseEstimation(U, P);
if actual != expected {
Message($"Incorrect eigenvalue for (U, |ψ⟩) = ({msg}): expected {expected}, got {actual}");
return false;
}
}

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

1. A single-qubit unitary $U$ that is guaranteed to have an eigenvalue $+1$ or $-1$
(with eigenphases $0$ or $\frac12$, respectively). The unitary will have its controlled variant defined.
2. A single-qubit state $\ket{\psi}$, given as a unitary $P$ that prepares it from the $\ket{0}$ state. In other words, the result of applying the unitary $P$ to the state $\ket{0}$ is the $\ket{\psi}$ state:
$$P\ket{0} = \ket{\psi}$$

**Output:** Return the eigenvalue which corresponds to the eigenstate $\ket{\psi}$ ($+1$ or $-1$).

<details>
<summary><b>Need a hint?</b></summary>
You can do this by allocating exactly two qubits and calling <code>Controlled U<\code> exactly once.
</details>
19 changes: 19 additions & 0 deletions katas/content/phase_estimation/one_bit_eigenphase/solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
What happens if you apply the unitary $U$ to the state $\ket{\psi}$? You end up in one of the states $\ket{\psi}$ or $-\ket{\psi}$, depending on whether the eigenvalue is $+1$ or $-1$. You need to distinguish these two scenarios, but they differ by a global phase, so you can't do this using just one qubit.

However, if you use the fact that you have access to controlled variant of $U$ and can allocate more than one qubit, you can use a variant of the phase kickback trick to distinguish these scenarios.

If you apply a controlled $U$ gate to two qubits: the control qubit in the $\ket{+}$ state and the target qubit in the state $\ket{\psi}$, you'll get the following state:

$$CU \ket{+}\ket{\psi} = \frac1{\sqrt2}(CU \ket{0}\ket{\psi} + CU \ket{1}\ket{\psi}) = \frac1{\sqrt2}(\ket{0}\ket{\psi} + \ket{1}U\ket{\psi}) = $$
$$= \frac1{\sqrt2}(\ket{0}\ket{\psi} + \ket{1}\lambda\ket{\psi}) =
\begin{cases}
\ket{+}\ket{\psi} \textrm{ if } \lambda = 1 \\
\ket{-}\ket{\psi} \textrm{ if } \lambda = -1
\end{cases}$$

You only need to measure the control qubit in the Hadamard basis to figure out whether its state is $\ket{+}$ or $\ket{-}$, and you'll be able to tell the value of the eigenvalue $\lambda$.

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