From a8b00c814201f9841ff316d1d1b9961ae7642a7a Mon Sep 17 00:00:00 2001 From: Mariia Mykhailova Date: Mon, 12 Aug 2024 16:10:43 -0700 Subject: [PATCH] Start migration of Phase Estimation kata (#1824) * First two exercises match the last two exercises from classic Linear Algebra kata, and follow a similar approach for migrating it to Q#, replacing Python code with a hardcoded exercise * Third exercise is 1.3 from classic PhaseEstimation kata, rewritten to work with Boolean checks rather than assertions. Part 2 will include exercises 2.1 and 1.4 from classic PhaseEstimation kata and new (to katas) QPE algorithm theory and demo of its success probability --------- Co-authored-by: Scott Carda <55811729+ScottCarda-MS@users.noreply.github.com> --- .../eigenvalues_s/Placeholder.qs | 9 ++ .../eigenvalues_s/Solution.qs | 8 ++ .../eigenvalues_s/Verification.qs | 26 ++++ .../phase_estimation/eigenvalues_s/index.md | 9 ++ .../eigenvalues_s/solution.md | 10 ++ .../eigenvectors_x/Placeholder.qs | 9 ++ .../eigenvectors_x/Solution.qs | 8 ++ .../eigenvectors_x/Verification.qs | 32 +++++ .../phase_estimation/eigenvectors_x/index.md | 10 ++ .../eigenvectors_x/solution.md | 12 ++ katas/content/phase_estimation/index.md | 123 ++++++++++++++++++ .../state_eigenvector/Placeholder.qs | 7 + .../state_eigenvector/Solution.qs | 12 ++ .../state_eigenvector/Verification.qs | 36 +++++ .../state_eigenvector/index.md | 14 ++ .../state_eigenvector/solution.md | 12 ++ 16 files changed, 337 insertions(+) create mode 100644 katas/content/phase_estimation/eigenvalues_s/Placeholder.qs create mode 100644 katas/content/phase_estimation/eigenvalues_s/Solution.qs create mode 100644 katas/content/phase_estimation/eigenvalues_s/Verification.qs create mode 100644 katas/content/phase_estimation/eigenvalues_s/index.md create mode 100644 katas/content/phase_estimation/eigenvalues_s/solution.md create mode 100644 katas/content/phase_estimation/eigenvectors_x/Placeholder.qs create mode 100644 katas/content/phase_estimation/eigenvectors_x/Solution.qs create mode 100644 katas/content/phase_estimation/eigenvectors_x/Verification.qs create mode 100644 katas/content/phase_estimation/eigenvectors_x/index.md create mode 100644 katas/content/phase_estimation/eigenvectors_x/solution.md create mode 100644 katas/content/phase_estimation/index.md create mode 100644 katas/content/phase_estimation/state_eigenvector/Placeholder.qs create mode 100644 katas/content/phase_estimation/state_eigenvector/Solution.qs create mode 100644 katas/content/phase_estimation/state_eigenvector/Verification.qs create mode 100644 katas/content/phase_estimation/state_eigenvector/index.md create mode 100644 katas/content/phase_estimation/state_eigenvector/solution.md diff --git a/katas/content/phase_estimation/eigenvalues_s/Placeholder.qs b/katas/content/phase_estimation/eigenvalues_s/Placeholder.qs new file mode 100644 index 0000000000..1e7b39963d --- /dev/null +++ b/katas/content/phase_estimation/eigenvalues_s/Placeholder.qs @@ -0,0 +1,9 @@ +namespace Kata { + open Microsoft.Quantum.Math; + + function EigenvaluesS() : Complex[] { + // Replace the return value with correct answer. + return [Complex(0.0, 0.0), + Complex(0.0, 0.0)]; + } +} diff --git a/katas/content/phase_estimation/eigenvalues_s/Solution.qs b/katas/content/phase_estimation/eigenvalues_s/Solution.qs new file mode 100644 index 0000000000..3399fb6dcf --- /dev/null +++ b/katas/content/phase_estimation/eigenvalues_s/Solution.qs @@ -0,0 +1,8 @@ +namespace Kata { + open Microsoft.Quantum.Math; + + function EigenvaluesS() : Complex[] { + return [Complex(1.0, 0.0), + Complex(0.0, 1.0)]; + } +} diff --git a/katas/content/phase_estimation/eigenvalues_s/Verification.qs b/katas/content/phase_estimation/eigenvalues_s/Verification.qs new file mode 100644 index 0000000000..e08fff40b7 --- /dev/null +++ b/katas/content/phase_estimation/eigenvalues_s/Verification.qs @@ -0,0 +1,26 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Math; + + function ComplexEqual(x : Complex, y : Complex) : Bool { + // Tests two complex numbers for equality. + AbsD(x::Real - y::Real) <= 0.001 and AbsD(x::Imag - y::Imag) <= 0.001 + } + + + @EntryPoint() + operation CheckSolution() : Bool { + let actual = Kata.EigenvaluesS(); + let expected = [Complex(1.0, 0.0), Complex(0.0, 1.0)]; + if Length(actual) != 2 { + Message("The array of eigenvalues should have exactly two elements."); + return false; + } + if ComplexEqual(actual[0], expected[0]) and ComplexEqual(actual[1], expected[1]) or + ComplexEqual(actual[0], expected[1]) and ComplexEqual(actual[1], expected[0]) { + Message("Correct!"); + return true; + } + Message("Incorrect value for one of the eigenvalues."); + return false; + } +} diff --git a/katas/content/phase_estimation/eigenvalues_s/index.md b/katas/content/phase_estimation/eigenvalues_s/index.md new file mode 100644 index 0000000000..e5eac268ff --- /dev/null +++ b/katas/content/phase_estimation/eigenvalues_s/index.md @@ -0,0 +1,9 @@ +**Input:** None. + +**Output:** Return an array of two eigenvalues of the $S$ gate. + +$$S = \begin{bmatrix} 1 & 0 \\ 0 & i \end{bmatrix}$$ + +Sort the eigenvalues in decreasing order of their real parts. + +> In this task, the eigenvalues are represented as complex numbers of Q# `Complex` type. diff --git a/katas/content/phase_estimation/eigenvalues_s/solution.md b/katas/content/phase_estimation/eigenvalues_s/solution.md new file mode 100644 index 0000000000..4abf745650 --- /dev/null +++ b/katas/content/phase_estimation/eigenvalues_s/solution.md @@ -0,0 +1,10 @@ +Since the $S$ gate is diagonal, it's easy to realize that its eigenvectors are the basis vectors $\ket{0}$ and $\ket{1}$. + +To find the corresponding eigenvalues, you need to solve the two equations: +- $S\ket{0} = \lambda \ket{0}$, which gives you $\lambda = 1$. +- $S\ket{1} = \lambda \ket{1}$, which gives you $\lambda = i$. + +@[solution]({ + "id": "phase_estimation__eigenvalues_s_solution", + "codePath": "Solution.qs" +}) diff --git a/katas/content/phase_estimation/eigenvectors_x/Placeholder.qs b/katas/content/phase_estimation/eigenvectors_x/Placeholder.qs new file mode 100644 index 0000000000..cd5e6e99c3 --- /dev/null +++ b/katas/content/phase_estimation/eigenvectors_x/Placeholder.qs @@ -0,0 +1,9 @@ +namespace Kata { + open Microsoft.Quantum.Math; + + function EigenvectorsX() : Double[][] { + // Replace the return value with correct answer. + return [[0.0, 0.0], + [0.0, 0.0]]; + } +} diff --git a/katas/content/phase_estimation/eigenvectors_x/Solution.qs b/katas/content/phase_estimation/eigenvectors_x/Solution.qs new file mode 100644 index 0000000000..501fe55795 --- /dev/null +++ b/katas/content/phase_estimation/eigenvectors_x/Solution.qs @@ -0,0 +1,8 @@ +namespace Kata { + open Microsoft.Quantum.Math; + + function EigenvectorsX() : Double[][] { + return [[1.0, 1.0], + [1.0, -1.0]]; + } +} diff --git a/katas/content/phase_estimation/eigenvectors_x/Verification.qs b/katas/content/phase_estimation/eigenvectors_x/Verification.qs new file mode 100644 index 0000000000..edc7499fb3 --- /dev/null +++ b/katas/content/phase_estimation/eigenvectors_x/Verification.qs @@ -0,0 +1,32 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Math; + + @EntryPoint() + operation CheckSolution() : Bool { + let actual = Kata.EigenvectorsX(); + if Length(actual) != 2 { + Message("The array of eigenvectors should have exactly two elements."); + return false; + } + for i in 0 .. 1 { + if Length(actual[i]) != 2 { + Message("Each eigenvector should have exactly two elements."); + return false; + } + if AbsD(actual[i][0]) + AbsD(actual[i][1]) < 1E-9 { + Message("Each eigenvector should be non-zero."); + return false; + } + } + + // One eigenvector has to have equal components, the other one - opposite ones + if AbsD(actual[0][0] - actual[0][1]) < 1e-9 and AbsD(actual[1][0] + actual[1][1]) < 1e-9 or + AbsD(actual[0][0] + actual[0][1]) < 1e-9 and AbsD(actual[1][0] - actual[1][1]) < 1e-9 { + Message("Correct!"); + return true; + } + + Message("Incorrect value for one of the eigenvectors."); + return false; + } +} diff --git a/katas/content/phase_estimation/eigenvectors_x/index.md b/katas/content/phase_estimation/eigenvectors_x/index.md new file mode 100644 index 0000000000..067fc7e195 --- /dev/null +++ b/katas/content/phase_estimation/eigenvectors_x/index.md @@ -0,0 +1,10 @@ +**Input:** None. + +**Output:** Return an array of two eigenvectors of the $X$ gate that correspond to different eigenvalues. + +$$X = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}$$ + +The eigenvectors don't have to be normalized, that is, they don't have to describe valid quantum states. +Both eigenvectors have to be non-zero. + +> In this task, the eigenvectors are represented as arrays of Q# `Double` type of length $2$. Your return should be an array of two such arrays. diff --git a/katas/content/phase_estimation/eigenvectors_x/solution.md b/katas/content/phase_estimation/eigenvectors_x/solution.md new file mode 100644 index 0000000000..2c631611f9 --- /dev/null +++ b/katas/content/phase_estimation/eigenvectors_x/solution.md @@ -0,0 +1,12 @@ +Since the $X$ gate is self-adjoint, you know that its eigenvalues can only be $+1$ and $-1$. +Now, you need to find the eigenvectors that correspond to these eigenvalues. +To do this, you need to solve the two equations: +- $X \begin{bmatrix} v_0 \\ v_1 \end{bmatrix} = \begin{bmatrix} v_0 \\ v_1 \end{bmatrix}$, which gives you $v_0 = v_1$. +- $X \begin{bmatrix} v_0 \\ v_1 \end{bmatrix} = -\begin{bmatrix} v_0 \\ v_1 \end{bmatrix}$, which gives you $v_0 = -v_1$. + +One of the eigenvectors should consist of two equal elements, and the other - of two elements with equal absolute values but opposite signs. + +@[solution]({ + "id": "phase_estimation__eigenvectors_x_solution", + "codePath": "Solution.qs" +}) diff --git a/katas/content/phase_estimation/index.md b/katas/content/phase_estimation/index.md new file mode 100644 index 0000000000..3b59aa8076 --- /dev/null +++ b/katas/content/phase_estimation/index.md @@ -0,0 +1,123 @@ +# Phase Estimation + +@[section]({ + "id": "phase_estimation__overview", + "title": "Overview" +}) + +This kata introduces you to the phase estimation algorithm - an important building block in more advanced quantum algorithms such as integer factoring. + +**This kata covers the following topics:** + +- The definition of eigenvalues and eigenvectors +- The phase estimation problem +- The quantum phase estimation algorithm based on quantum Fourier transform + +**What you should know to start working on this kata:** + +- Basic quantum gates and measurements. +- Quantum Fourier transform. + +@[section]({ + "id": "phase_estimation__eigen", + "title": "Eigenvectors, Eigenvalues, and Eigenphases" +}) + +An *eigenvector* of a matrix $A$ is a non-zero vector that, when multiplied by that matrix, changes by a scalar factor: + +$$A \ket{v} = \lambda \ket{v}$$ + +The number $\lambda$ is called an *eigenvalue* that corresponds to this eigenvector. In general, eigenvalues of matrices can be complex numbers. + +Recall that all quantum gates are unitary matrices, for which their inverse equals their adjoint ($U^{-1} = U^\dagger$). This means that the eigenvalues of their eigenvectors have the property that their modulus equals $1$: + +$$|\lambda| = 1$$ + +Thus, they can be written in the following form: +$$\lambda = e^{i\theta}$$ + +The value $\theta$ is called an *eigenphase* that corresponds to this eigenvector. + +> How can you prove that the modulus of an eigenvalue of a unitary matrix equals $1$? +> +> On one hand, by definition of an eigenvalue, +> $$U \ket{v} = \lambda \ket{v}$$ +> $$|U \ket{v}| = |\lambda| \cdot |\ket{v}|$$ +> On the other hand, using the properties of the unitary matrix, you can write the following equation: +> $$|U \ket{v}|^2 = \bra{v} U^\dagger U \ket{v} = \bra{v} U^{-1} U \ket{v} = \bra{v} I \ket{v} = \braket{v|v} = |\ket{v}|^2$$ +> +> From these two equations, you get the following equality: +> $$(|\lambda| \cdot |\ket{v}|)^2 = |\ket{v}|^2$$ +> And then, finally: +> $$|\lambda| = 1$$ + +If the quantum gate is self-adjoint, that is, its matrix equals its inverse $U^{-1} = U$, the eigenvalues of this matrix can only be $+1$ and $-1$, with eigenphases $0$ and $\pi$, respectively. + +> You can prove this in a similar manner, using the defintion of an eigenvalue: +> $$U^2 \ket{v} = U(U \ket{v}) = U(\lambda \ket{v}) = \lambda U \ket{v} = \lambda^2 \ket{v}$$ +> At the same time, +> $$U^2 \ket{v} = UU \ket{v} = U^{-1}U \ket{v} = I \ket{v} = \ket{v}$$ +> So you can conclude that $\lambda^2 = 1$. + +For example, the $Z$ gate has two eigenvctors: +- $\ket{0}$, with eigenvalue $1$ +- $\ket{1}$, with eigenvalue $-1$ + + +@[exercise]({ + "id": "phase_estimation__eigenvalues_s", + "title": "Find Eigenvalues of the S Gate", + "path": "./eigenvalues_s/" +}) + +@[exercise]({ + "id": "phase_estimation__eigenvectors_x", + "title": "Find Eigenvectors of the X Gate", + "path": "./eigenvectors_x/" +}) + +@[exercise]({ + "id": "phase_estimation__state_eigenvector", + "title": "Is Given State an Eigenvector of the Gate?", + "path": "./state_eigenvector/" +}) + + +@[section]({ + "id": "phase_estimation__problem", + "title": "Phase Estimation Problem" +}) + +The phase estimation problem is formulated as follows. + +You are given a unitary operator $U$ and its eigenvector $\ket{\psi}$. The eigenvector is given as a unitary operator $P$ that, when applied to $\ket{0}$, results in the state $\ket{\psi}$. + +Your goal is to find the eigenvalue $\lambda$ associated with this eigenvector, or, in a more common formulation, the corresponding eigenphase $\theta$: + +$$U\ket{\psi} = e^{2 \pi i \theta} \ket{\psi}, \theta = ?$$ + +The value of $\theta$ is defined to be between $0$ and $1$, since any value outside of this range has an equivalent value within it. Instead of representing $\theta$ as a decimal, sometimes it is represented as a binary fraction with $n$ digits: + +$$\theta = 0.\theta_1 \theta_2... \theta_n = \frac{\theta_1}{2^1}+ \frac{\theta_2}{2^2}+...\frac{\theta_n}{2^n}$$ + +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 + + +@[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) + + +@[section]({ + "id": "phase_estimation__conclusion", + "title": "Conclusion" +}) + +Congratulations! In this kata you learned about the phase estimation problem and its solution using the quantum phase estimation algorithm. diff --git a/katas/content/phase_estimation/state_eigenvector/Placeholder.qs b/katas/content/phase_estimation/state_eigenvector/Placeholder.qs new file mode 100644 index 0000000000..170a182def --- /dev/null +++ b/katas/content/phase_estimation/state_eigenvector/Placeholder.qs @@ -0,0 +1,7 @@ +namespace Kata { + operation IsEigenvector(U : Qubit => Unit, P : Qubit => Unit is Adj) : Bool { + // Implement your solution here... + + return false; + } +} diff --git a/katas/content/phase_estimation/state_eigenvector/Solution.qs b/katas/content/phase_estimation/state_eigenvector/Solution.qs new file mode 100644 index 0000000000..e1ffe8d0e9 --- /dev/null +++ b/katas/content/phase_estimation/state_eigenvector/Solution.qs @@ -0,0 +1,12 @@ +namespace Kata { + import Microsoft.Quantum.Diagnostics.CheckZero; + operation IsEigenvector(U : Qubit => Unit, P : Qubit => Unit is Adj) : Bool { + use q = Qubit(); + P(q); + U(q); + Adjoint P(q); + let ret = CheckZero(q); + Reset(q); + return ret; + } +} diff --git a/katas/content/phase_estimation/state_eigenvector/Verification.qs b/katas/content/phase_estimation/state_eigenvector/Verification.qs new file mode 100644 index 0000000000..46140f03c3 --- /dev/null +++ b/katas/content/phase_estimation/state_eigenvector/Verification.qs @@ -0,0 +1,36 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Unstable.StatePreparation; + + @EntryPoint() + operation CheckSolution() : Bool { + let eigenvectors = [ + (Z, I, "Z, |0⟩"), + (Z, X, "Z, |1⟩"), + (S, I, "S, |0⟩"), + (S, X, "S, |1⟩"), + (X, H, "X, |+⟩"), + (X, q => PreparePureStateD([1., -1.], [q]), "X, |-⟩")]; + for (U, P, msg) in eigenvectors { + if not Kata.IsEigenvector(U, P) { + Message($"Incorrect for (U, P) = ({msg}): expected true"); + return false; + } + } + + let notEigenvectors = [ + (Z, H, "Z, |+⟩"), + (X, X, "X, |1⟩"), + (X, Z, "X, |0⟩"), + (Y, H, "Y, |+⟩"), + (Y, X, "Y, |1⟩")]; + for (U, P, msg) in notEigenvectors { + if Kata.IsEigenvector(U, P) { + Message($"Incorrect for (U, |ψ⟩) = ({msg}): expected false"); + return false; + } + } + + Message("Correct!"); + return true; + } +} diff --git a/katas/content/phase_estimation/state_eigenvector/index.md b/katas/content/phase_estimation/state_eigenvector/index.md new file mode 100644 index 0000000000..37ed00852a --- /dev/null +++ b/katas/content/phase_estimation/state_eigenvector/index.md @@ -0,0 +1,14 @@ +**Inputs:** + +1. A single-qubit unitary $U$. +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 true if the given state is an eigenstate of the given unitary, and false otherwise. + +
+ Need a hint? + +The library operation CheckZero allows you to check whether the state of the given qubit is $\ket{0}$. + +
\ No newline at end of file diff --git a/katas/content/phase_estimation/state_eigenvector/solution.md b/katas/content/phase_estimation/state_eigenvector/solution.md new file mode 100644 index 0000000000..30939ba09f --- /dev/null +++ b/katas/content/phase_estimation/state_eigenvector/solution.md @@ -0,0 +1,12 @@ +A quantum state is an eigenstate of a quantum gate if applying that gate to that state doesn't change it, other than multiply it by a global phase. This means that your solution should probably start by preparing the state $\ket{\psi}$ and applying the unitary $U$ to it. How can you check that the state after that is still $\ket{\psi}$ (up to a global phase)? + +Let's consider what happens if you apply the adjoint of $P$ to the state $\ket{\psi}$: + +$$P^\dagger \ket{\psi} = P^\dagger P\ket{0} = I\ket{0} = \ket{0}$$ + +You can use this to finish the solution: apply `Adjoint P` to the state you obtained after applying $U$ and check whether the result is $\ket{0}$ using the library operation `CheckZero`. + +@[solution]({ + "id": "phase_estimation__state_eigenvector_solution", + "codePath": "Solution.qs" +})