diff --git a/css/grover-figures.css b/css/grover-figures.css index c152cfb..301156f 100644 --- a/css/grover-figures.css +++ b/css/grover-figures.css @@ -26,13 +26,83 @@ div.svg-fig svg#fig1 #tooltip text { font-size: 13.5px; } -.graph-rect { +div.svg-fig svg#fig2 .graph-rect { fill: black; opacity: 0.85; } -.mean-line { +div.svg-fig svg#fig2 .mean-line { stroke-width: 0.85; stroke: red; stroke-dasharray: 3, 1; +} + +.info-div { + text-align: right; + background-color: #F0F0F0; + display: inline-block; + float: right; + padding: 3px 5px; + border-radius: 2px; + pointer-events: none; +} + +#steps-list { + float: left; + width: 600px; + display: block; + padding: 0; + padding-left: 35px; +} + +#steps-container { + width: 110%; + position: relative; + overflow: hidden; + display: block; + padding: 0; + margin-top: 0; + margin-bottom: 0; +} + +#fig2-container { + float: left; + width: 580px; + margin-left: 45px; + padding: 0; + opacity: 0; +} + +#fig2-container.graph-scroll-fixed { + position: fixed; + display: block; + top: 30%; + left: calc(600px - 150px + 45px + 35px + 12.5%); +} + +#fig2-container.graph-scroll-below { + position: absolute; + bottom: 0px; + margin-left: calc(600px - 150px + 35px + 32px + 12.5%); +} + +.play-icon { + margin: 0 0 -0.6rem 0; + opacity: 0.6; + width: 1.85rem; + display: inline-block; +} + +a.play-icon.in-list { + float: right; + margin-right: 15px; +} + +a.play-icon.in-list:hover { + opacity: 0.8; +} + +a.play-icon.in-list:active { + width: 2.1rem; + margin-right: 12px; } \ No newline at end of file diff --git a/css/tufte.css b/css/tufte.css index 7b09d6c..cf53c19 100644 --- a/css/tufte.css +++ b/css/tufte.css @@ -8,7 +8,8 @@ body { width: 87.5%; font-family: 'Computer Modern Serif'; padding-bottom: 20rem; color: #000; /* Changed to black */ - max-width: 1200px; } + max-width: 1200px; + overflow-x: hidden; } h1 { font-family: 'Computer Modern Serif'; font-weight: bold; diff --git a/fig2work.html b/fig2work.html index 4114d01..6b28bd8 100644 --- a/fig2work.html +++ b/fig2work.html @@ -33,7 +33,7 @@ + -

Grover’s Search Algorithm

-

Grover’s algorithm is a quantum search algorithm, discovered by Lov Grover [1]. A search algorithm, given some database of things, aims to find one specific item - a “needle in a haystack”.

+
+ +

Grover’s Search Algorithm

+ +

+ Grover’s algorithm is a quantum search algorithm, discovered by Lov Grover [1]. A search algorithm, given some database of things, aims to find one specific item - a “needle in a haystack”. +

+

Constructing the Algorithm

-

One of many full descriptions of the algorithm is given in the excellent book by Nielsen and Chuang [2] but we summarise it briefly here. We set up the algorithm as follows.

-

We say that there are \(N\) items in our database, and we index them accordingly (although their order is unimportant) by the integers in the range \(0\) to \(N-1\), calling this index \(x\). We can represent any value of \(x\) with \(n\) classical bits11. We do this by using the ordering of the sequence 0s and 1s; each consecutive digit in the sequence represents an decreasing (starting from \(n-1\)) power of 2, and the integer is given by the sum of these powers of 2 multiplied by the digit. An example makes this clear: \[ 0101 \rightarrow 0 \cdot 2^3 + 1 \cdot 2^2 + 0 \cdot 2^1 + 1 \cdot 2^0 = 4 + 1 = 5 \] , where \(2^{n-1} \leq N \leq 2^n - 1\), and so for convenience we choose \(N\) to be some integer power \(n\) of 2. We identify the specific item we want - the needle - as having the index \(x_0\) and define an oracle function \(f\):

-

\[f(x)= -\begin{cases} -1 & x = x_0 \\ -0 & x \neq x_0 -\end{cases}\]

-

And so the search problem is to apply the oracle to successive values of \(x\) until it returns 1 (the needle is found). For our interactive demonstration, select a needle in Fig. 1.

+

+ One of many full descriptions of the algorithm is given in the excellent book by Nielsen and Chuang [2] but we summarise it briefly here. We set up the algorithm as follows. +

+ +

+ We say that there are \(N\) items in our database, and we index them accordingly (although their order is unimportant) by the integers in the range \(0\) to \(N-1\), calling this index \(x\). We can represent any value of \(x\) with \(n\) classical bits1 + + + 1. We do this by using the ordering of the sequence 0s and 1s; each consecutive digit in the sequence represents an decreasing (starting from \(n-1\)) power of 2, and the integer is given by the sum of these powers of 2 multiplied by the digit. An example makes this clear: + + \[ 0101 \rightarrow 0 \cdot 2^3 + 1 \cdot 2^2 + 0 \cdot 2^1 + 1 \cdot 2^0 = 4 + 1 = 5 \] + + + + , where \(2^{n-1} \leq N \leq 2^n - 1\), and so for convenience we choose \(N\) to be some integer power \(n\) of 2. We identify the specific item we want - the needle - as having the index \(x_0\) and define an oracle function \(f\): +

+

+ + \[f(x)= + \begin{cases} + 1 & x = x_0 \\ + 0 & x \neq x_0 + \end{cases}\] + +

+

+ And so the search problem is to apply the oracle to successive values of \(x\) until it returns 1 (the needle is found). For our interactive demonstration, select a needle in Fig. 1. +

@@ -54,91 +94,363 @@

Constructing the Algorithm

Figure 1. We will illustrate the Grover search for the case of \(N=16\) (ie with 4 qubits). Select the needle that we will find by clicking on the respective circle above. You can see the binary representation of its index by hovering over the circle.
- -

As this is a quantum algorithm, we need to make the oracle work on quantum states and so we define an oracle operator \(O\):

-

\[O( \ket{x} \ket{b} ) = \ket{x} \ket{b \oplus f(x)}\]

-

Where \(\ket{b}\) is the oracle qubit and \(\oplus\) denotes binary modular addition22. Modular addition is like normal addition except the number line is periodic - when we reach a certain value we go back to the beginning. For example, on a 12-hour clock if we add 6 hours to 11 then rather than getting 17, we get \(11 + 6 \pmod {12} = 5\). Our oracle operator, as with most of information theory, uses addition modulo 2 - the only numbers we’re allowed are 0 and 1! For example, \(1 \oplus 0 = 1 \pmod 2 = 1\), but \(1 \oplus 1 = 2 \pmod 2 = 0\)..

-

Now we perform the first “quantum” step: say that we put the oracle qubit \(\ket{b}\) in the \(\ket{-}\) state, equal to \((\ket{0} - \ket{1})/\sqrt{2}\). Now the action of the oracle operator is:

-

\[\begin{align} -O( \ket{x} \ket{-} ) & = \frac{1}{\sqrt{2}} \ket{x} \left( \ket{0 \oplus f(x)} - \ket{1 \oplus f(x)} \right) \\ -& = (-1)^{f(x)} \ket{x} \left( \frac{\ket{0} - \ket{1}}{\sqrt{2}} \right) \\ -& = (-1)^{f(x)} \ket{x} \ket{-} -\end{align}\]

-

So, for the special case of the oracle qubit being \(\ket{-}\), we can say that the oracle operator flips the phase of the index bit \(\ket{x}\) if and only if it is the needle (ie \(x=x_0\)). As the oracle qubit is unchanged by this operation, we can effectively ignore it.

+

+ As this is a quantum algorithm, we need to make the oracle work on quantum states and so we define an oracle operator \(O\): +

+

+ + \[O( \ket{x} \ket{b} ) = \ket{x} \ket{b \oplus f(x)}\] + +

+

+ Where \(\ket{b}\) is the oracle qubit and \(\oplus\) denotes binary modular addition2 + + + 2. Modular addition is like normal addition except the number line is periodic - when we reach a certain value we go back to the beginning. For example, on a 12-hour clock if we add 6 hours to 11 then rather than getting 17, we get \(11 + 6 \pmod {12} = 5\). Our oracle operator, as with most of information theory, uses addition modulo 2 - the only numbers we’re allowed are 0 and 1! For example, \(1 \oplus 0 = 1 \pmod 2 = 1\), but \(1 \oplus 1 = 2 \pmod 2 = 0\). + + + . +

+

+ Now we perform the first “quantum” step: say that we put the oracle qubit \(\ket{b}\) in the \(\ket{-}\) state, equal to \((\ket{0} - \ket{1})/\sqrt{2}\). Now the action of the oracle operator is: +

+

+ + \[\begin{align} + O( \ket{x} \ket{-} ) & = \frac{1}{\sqrt{2}} \ket{x} \left( \ket{0 \oplus f(x)} - \ket{1 \oplus f(x)} \right) \\ + & = (-1)^{f(x)} \ket{x} \left( \frac{\ket{0} - \ket{1}}{\sqrt{2}} \right) \\ + & = (-1)^{f(x)} \ket{x} \ket{-} + \end{align}\] + +

+

+ So, for the special case of the oracle qubit being \(\ket{-}\), we can say that the oracle operator flips the phase of the index bit \(\ket{x}\) if and only if it is the needle (ie \(x=x_0\)). As the oracle qubit is unchanged by this operation, we can effectively ignore it. +

+ -

Now that we have set up the search problem and provided an operator that can change the phase of the needle qubit, we can give the procedure for the Grover search33. We use the obvious extension of the binary notation explained in the first sidenote: \[\ket{x} \equiv \ket{x_1 x_2 \cdots x_n}\] where \[x = x_1 2^{n-1} + x_2 2^{n-2} + \cdots + x_n 2^0\] For example, \(\ket{5} \equiv \ket{0101}\). The important thing here is to remember that some state \(\ket{x}\) is not a state of a single qubit; it is a state of \(n\) qubits.. Steps 1 and 2 are just to get the system in an appropriate starting state, and step 3 is where the “magic” happens.

-
    -
  1. For a haystack size of \(N = 2^n\), prepare \(n+1\) qubits in the ground state: \[\ket{\psi_1} = \ket{0}^{\otimes n} \ket{0}\]
  2. -
  3. Put the first \(n\) qubits (the haystack qubits) in a state of uniform superposition, and then put the last qubit, the oracle qubit, in the state described above. That is to say, we apply \((H^{\otimes n} \otimes H X)\) to all of the qubits. \[\ket{\psi_2} = \frac{1}{\sqrt{N}} \sum_{x=0}^{N-1} \ket{x} \ket{-}\]
  4. -
  5. The Grover Iteration - perform this step \(R\) times44. We will discuss the value of R later   -
      -
    1. Apply the oracle operator \(O\), defined above, to all the qubits.
    2. -
    3. Apply the Hadamard transform \(H^{\otimes n}\) to the haystack qubits.
    4. -
    5. Multiply all states which are not \(\ket{0}\) by \(-1\). This can be written as an operator, \(2 \ket{0}\bra{0} - I\), where \(I\) is the identity matrix of dimension \(N\).
    6. -
    7. Apply the \(H^{\otimes n}\) to the haystack qubits again.
       
    8. -
    -It can be shown55. Applying steps i-iv is equivalent to applying: \[\begin{align} -& \: H^{\otimes n}(2 \ket{0} \bra{0} - I)H^{\otimes n} O \\ -= & \: (2 H^{\otimes n} \ket{0} \bra{0} H^{\otimes n} - H^{\otimes n} H^{\otimes n}) O -\end{align}\] We then recall that in this case \(\ket{0}\) is really representing an \(n\)-qubit state \(\ket{00 \cdots 0}\), and also that \(H \ket{0} = (\ket{0} + \ket{1})/\sqrt{2}\), so that: \[\begin{align} -H^{\otimes n} \ket{0} & = \overbrace{(H \ket{0}) \otimes (H \ket{0}) \otimes \cdots \otimes (H \ket{0})}^{n \text{ times}} \\ -& = \frac{1}{\left(\sqrt{2}\right)^n} \Big( [\ket{0} + \ket{1}] \otimes [\ket{0} + \ket{1}] \otimes \cdots \otimes [\ket{0} + \ket{1}] \Big) \\ -& = \frac{1}{\sqrt{2^n}} \sum \big\{ \text{All permutations of } n \text{ 1s and 0s} \big\} \\ -& = \frac{1}{\sqrt{N}} \sum_{x=0}^{N-1} \ket{x} = \ket{\phi} -\end{align}\] Finally, remember that \(\bra{\chi} A^\dagger = (A \ket{\chi})^\dagger\), that \(H = H^\dagger\), and that \(H^2 = I\). Then we can write: \[\begin{align} - & \: (2 H^{\otimes n} \ket{0} \bra{0} H^{\otimes n} - H^{\otimes n} H^{\otimes n}) O \\ -= & \: (2 (H^{\otimes n} \ket{0})(H^{\otimes n} \ket{0})^\dagger - I) O \\ -= & \: (2 \ket{\phi} \bra{\phi} - I) O \qquad \qquad \qquad \qquad \Box -\end{align}\] that the above steps \(i-iv\) can be written as a single operator: \[(2\ket{\phi} \bra{\phi} - I)O\] Where \(\ket{\phi}\) is the uniform superposition of the haystack qubits (ie \(\ket{\psi_2}\) without the oracle qubit): \[\ket{\phi} = \frac{1}{\sqrt{N}} \sum_{x=0}^{N-1} \ket{x}\] It is extremely useful to note that the part of this operation in the brackets can be seen geometrically as an inversion about the mean. Let’s consider why this is. The operator \(F = 2(\ket{\phi} \bra{\phi} - I)\) leaves \(\ket{\phi}\) invariant and flips the sign of any state orthogonal to \(\ket{\phi}\). As \(\ket{\phi}\) is the uniform superposition over all \(n\)-qubit states, the overlap of it and any other \(n\)-qubit state expressed in the computational basis, \(\ket{\psi} = \sum_x a_x \ket{x}\), is simply the sum of \(\ket{\psi}\)’s eigenvalues multiplied by \(\ket{\phi}\)’s normalisation factor, \(1/\sqrt{N}\): \[\bra{\phi} \psi \rangle = \frac{1}{\sqrt{N}}\sum_x a_x\] It follows that: \[\begin{align} -F \ket{\psi} & = (2(\ket{\phi} \bra{\phi} - I)) \ket{\psi} \\ -& = 2 \bra{\phi} \psi \rangle \ket{\phi} - \ket{\psi} \\ -& = 2 \sum_x \left( \frac{1}{\sqrt{N}} \frac{1}{\sqrt{N}} \sum_i a_i \right) \ket{x} - \sum_x a_x \ket{x} \\ -& = \sum_x \Bigg[ 2 \underbrace{\left( \frac{1}{N} \sum_i a_i \right)}_{\text{ (mean of the } a_i \text{)}} - a_x \Bigg] \ket{x} -\end{align}\] Which clearly maps amplitudes across the mean.
  6. -
  7. After our repeated application of step 3, we should have \[\ket{\psi_4} \approx \ket{x_0}\ket{-}\] And so we measure the \(n\) haystack qubits (ignoring the oracle qubit as its work is done) and obtain the needle \(x_0\) with high probability.
  8. -
-

The repeated sign changes of the needle state combined with the flips about the mean increase the needle state’s amplitude compared to the rest of the haystack. When this is sufficiently high, a measurement on the haystack qubits will return the needle state with high probability (\(\propto [\text{needle amplitude}]^2\)).

+ +

+ Now that we have set up the search problem and provided an operator that can change the phase of the needle qubit, we can give the procedure for the Grover search3 + + + 3. We use the obvious extension of the binary notation explained in the first sidenote: + + + \[\ket{x} \equiv \ket{x_1 x_2 \cdots x_n}\] + + + where + + + \[x = x_1 2^{n-1} + x_2 2^{n-2} + \cdots + x_n 2^0\] + + + For example, \(\ket{5} \equiv \ket{0101}\). The important thing here is to remember that some state \(\ket{x}\) is not a state of a single qubit; it is a state of \(n\) qubits. + + + . Steps 1 and 2 are just to get the system in an appropriate starting state, and step 3 is where the “magic” happens. +

+ +
+
    + +
  1. + For a haystack size of \(N = 2^n\), prepare \(n+1\) qubits in the ground state: + + + play_circle_filled + + + + \[\ket{\psi_1} = \ket{0}^{\otimes n} \ket{0}\] + + +
  2. +
  3. + Put the first \(n\) qubits (the haystack qubits) in a state of uniform superposition, and then put the last qubit, the oracle qubit, in the state described above. That is to say, we apply \((H^{\otimes n} \otimes H X)\) to all of the qubits. + + + play_circle_filled + + + + \[\ket{\psi_2} = \frac{1}{\sqrt{N}} \sum_{x=0}^{N-1} \ket{x} \ket{-}\] + +
  4. +
  5. + The Grover Iteration - perform this step \(R\) times, where the value of \(R\) is yet to be specified. +   +
    +
      +
    1. + Apply the oracle operator \(O\), defined above, to all the qubits. + + + play_circle_filled + + +
    2. +
    3. + Apply the Hadamard transform \(H^{\otimes n}\) to the haystack qubits. +
    4. +
    5. + Multiply all states which are not \(\ket{0}\) by \(-1\). This can be written as an operator, \(2 \ket{0}\bra{0} - I\), where \(I\) is the identity matrix of dimension \(N\). +
    6. +
    7. + Apply the \(H^{\otimes n}\) to the haystack qubits again. +
    8. +
    +
    + + It can be shown5 + + + + that the above steps \(i-iv\) can be written as a single operator: + + + \[(2\ket{\phi} \bra{\phi} - I)O\] + + + Where \(\ket{\phi}\) is the uniform superposition of the haystack qubits (ie \(\ket{\psi_2}\) without the oracle qubit): + + + \[\ket{\phi} = \frac{1}{\sqrt{N}} \sum_{x=0}^{N-1} \ket{x}\] + + + It is extremely useful to note that the part of this operation in the brackets (steps \(ii-iv\)) can be seen geometrically as an inversion about the mean. + + + play_circle_filled + + +

    + + Let’s consider why this is. The operator \(F = 2(\ket{\phi} \bra{\phi} - I)\) leaves \(\ket{\phi}\) invariant and flips the sign of any state orthogonal to \(\ket{\phi}\). As \(\ket{\phi}\) is the uniform superposition over all \(n\)-qubit states, the overlap of it and any other \(n\)-qubit state expressed in the computational basis, \(\ket{\psi} = \sum_x a_x \ket{x}\), is simply the sum of \(\ket{\psi}\)’s eigenvalues multiplied by \(\ket{\phi}\)’s normalisation factor, \(1/\sqrt{N}\): + + + \[\bra{\phi} \psi \rangle = \frac{1}{\sqrt{N}}\sum_x a_x\] + + + It follows that: + + + \[\begin{align} + F \ket{\psi} & = (2(\ket{\phi} \bra{\phi} - I)) \ket{\psi} \\ + & = 2 \bra{\phi} \psi \rangle \ket{\phi} - \ket{\psi} \\ + & = 2 \sum_x \left( \frac{1}{\sqrt{N}} \frac{1}{\sqrt{N}} \sum_i a_i \right) \ket{x} - \sum_x a_x \ket{x} \\ + & = \sum_x \Bigg[ 2 \underbrace{\left( \frac{1}{N} \sum_i a_i \right)}_{\text{ (mean of the } a_i \text{)}} - a_x \Bigg] \ket{x} + \end{align}\] + + + Which clearly maps amplitudes across the mean. +
  6. +
  7. + After our repeated application of step 3, we should have + + + \[\ket{\psi_4} \approx \ket{x_0}\ket{-}\] + + + And so we measure the \(n\) haystack qubits (ignoring the oracle qubit as its work is done) and almost certainly obtain the needle \(x_0\). +
    +
    + This is because the repeated sign changes of the needle state combined with the flips about the mean increase the needle state’s amplitude compared to the rest of the haystack. When this is sufficiently high, a measurement on the haystack qubits will return the needle state with high probability (\(\propto [\text{needle amplitude}]^2\)). +
  8. +
+ + +
+ + +
+
\(P(x_0) \approx \) 
+
\(R = \) 
+
+ +
+ Figure 2. A graphical representation of the steps listed on the left. Click the + + + play_circle_filled + + + in each step to show it here. Note that you will need to repeat the actions that comprise step 3 until the needle state has been sufficiently amplified! Try to find the optimal value of \(R\) for this system. +
+
+
+
+
+ +
+ +

+ + 5. Applying steps i-iv is equivalent to applying: + + + \[\begin{align} + & \: H^{\otimes n}(2 \ket{0} \bra{0} - I)H^{\otimes n} O \\ + = & \: (2 H^{\otimes n} \ket{0} \bra{0} H^{\otimes n} - H^{\otimes n} H^{\otimes n}) O + \end{align}\] + + + We then recall that in this case \(\ket{0}\) is really representing an \(n\)-qubit state \(\ket{00 \cdots 0}\), and also that \(H \ket{0} = (\ket{0} + \ket{1})/\sqrt{2}\), so that: + + + \[\begin{align} + H^{\otimes n} \ket{0} & = \overbrace{(H \ket{0}) \otimes (H \ket{0}) \otimes \cdots \otimes (H \ket{0})}^{n \text{ times}} \\ + & = \frac{1}{\left(\sqrt{2}\right)^n} \Big( [\ket{0} + \ket{1}] \otimes [\ket{0} + \ket{1}] \otimes \cdots \otimes [\ket{0} + \ket{1}] \Big) \\ + & = \frac{1}{\sqrt{2^n}} \sum \big\{ \text{All permutations of } n \text{ 1s and 0s} \big\} \\ + & = \frac{1}{\sqrt{N}} \sum_{x=0}^{N-1} \ket{x} = \ket{\phi} + \end{align}\] + + + Finally, remember that \(\bra{\chi} A^\dagger = (A \ket{\chi})^\dagger\), that \(H = H^\dagger\), and that \(H^2 = I\). Then we can write: + + + \[\begin{align} + & \: (2 H^{\otimes n} \ket{0} \bra{0} H^{\otimes n} - H^{\otimes n} H^{\otimes n}) O \\ + = & \: (2 (H^{\otimes n} \ket{0})(H^{\otimes n} \ket{0})^\dagger - I) O \\ + = & \: (2 \ket{\phi} \bra{\phi} - I) O \qquad \qquad \qquad \qquad \Box + \end{align}\] + + +

+

How many iterations?

-

We said that it is necessary to apply step 3 some number \(R\) of times in order to obtain the needle state with sufficiently high probability. We’ll now look at what value \(R\) might take.

-

First, write our initial state as: \[\ket{\psi} = \nu_0 \ket{x_0} + \sum_{x \neq x_0} \eta_0 \ket{x}\] Where the subscripts on \(\nu\) and \(\eta\) denote the number of Grover iterations performed. Referring to Step 2 of the above, note that \(\nu_0 = \eta_0 = 1/\sqrt{N}\). Now consider the action of the Grover iteration on the \(\nu_j\) and \(\eta_j\):

-
    -
  1. Action of the oracle is denoted by a prime: \[\nu_j^\prime = -\nu_j\]
  2. -
  3. Inversion about the mean. Call the mean \(\mu\): \[\mu_i = \frac{1}{N}(\nu_i + (N-1)\eta_i) = \frac{\nu_i - \eta_i}{N} + \eta_i\] And so we can see that \[\begin{align} -\nu_{j+1} & = 2\mu_j^\prime - \nu_j^\prime \\ -& = 2\frac{\nu_j^\prime - \eta_j}{N} + 2\eta_j - \nu_j^\prime \\ -& = \frac{N-2}{N} \nu_j + \frac{2(N-1)}{N} \eta_j \\ -\eta_{j+1} & = 2\mu_j^\prime - \eta_j \\ -& = 2\frac{\nu_j^\prime - \eta_j}{N} + 2\eta_j - \eta_j \\ -& = -\frac{2}{N} \nu_j + \frac{N-2}{N} \eta_j -\end{align}\]
  4. -
-

It is rather difficult to solve these recurrence relations, but their solutions are given in Ref [3]. The solutions are: \[\begin{align} \nu_j & = \sin([2j+1]\theta) \\ \eta_j & = \frac{1}{\sqrt{N-1}}\cos([2j+1]\theta) \end{align}\] Where \(\sin^2 \theta = 1/N\). The validity of these solutions can be proven by induction66. We prove as follows.
Basis case. Set \(j=0\). Now insert into the solution: \[\begin{align} \nu_0 & = \sin(\theta) \\ -& = \sin(\arcsin(\frac{1}{\sqrt{N}})) \\ -& = \frac{1}{\sqrt{N}} \\ -\eta_0 & = \frac{1}{\sqrt{N-1}}\cos(\theta) \\ -& = \frac{1}{\sqrt{N-1}}\cos(\arcsin(\frac{1}{\sqrt{N}})) \\ -& = \frac{1}{\sqrt{N-1}} \sqrt{1 - \frac{1}{N}} \\ -& = \frac{1}{\sqrt{N-1}} \sqrt{\frac{N-1}{N}} = \frac{1}{\sqrt{N}} -\end{align}\] Which satisfy the boundary conditions we noted in the main text.
Inductive step. Assume the solution is valid for \(j\), now test for \(j+1\): \[\begin{align} \nu_{j+1} & = \sin([2(j+1)+1]\theta) \\ -& = \sin([2j + 1]\theta + 2\theta) \\ -& = \sin([2j + 1]\theta) \cos(2\theta) + \cos([2j + 1]\theta) \sin(2 \theta) \\ -& = \nu_j (\cos^2\theta - \sin^2\theta) + \sqrt{N-1} \eta_j (2 \cos\theta \sin\theta) \\ -& = \nu_j (\frac{N-1}{N} - \frac{1}{N}) + \sqrt{N-1} \eta_j (2 \sqrt{\frac{N-1}{N}} \frac{1}{\sqrt{N}}) \\ -& = \frac{N-2}{N} \nu_j + \frac{2(N-1)}{N} \eta_j \\ -\eta_{j+1} & = \frac{1}{\sqrt{N-1}} \cos([2(j+1) + 1]\theta) \\ -& = \frac{1}{\sqrt{N-1}} \cos([2j + 1]\theta + 2 \theta) \\ -& = \frac{1}{\sqrt{N-1}} \big( \cos([2j+1]\theta) \cos(2\theta) - \sin([2j+1]\theta) \sin(2\theta) \big) \\ -& = \frac{1}{\sqrt{N-1}} ( \sqrt{N-1} \eta_j \frac{N-2}{N} - 2 \nu_j \frac{\sqrt{N-1}}{N} ) \\ -& = -\frac{2}{N} \nu_j + \frac{N-2}{N} \eta_j -\end{align}\] Q.E.D.
. As we want to maximise the probability of measuring the needle, we clearly want to maximise \(\nu_j\). The maximum of \(\sin(x)\) is at \(x = \pi/2\) and so we want \((2j + 1)\theta = \pi/2\). Considering that \(j\) must be an integer, we want: \[j = \left \lfloor \frac{\pi - 2\theta}{4\theta} \right \rfloor\] Now see that since \(\sin\theta \approx \theta\) for small \(\theta\) and recalling that \(\sin\theta = 1/\sqrt{N}\): \[j \approx \left \lfloor \frac{\pi - 2/\sqrt{N}}{4/\sqrt{N}} \right \rfloor \approx \left \lfloor \frac{\pi}{4}\sqrt{N} \right \rfloor\] And thus we choose \(R\) to be the above value. Note an interesting - and characteristically quantum - implication of the sinusoidal form for \(\nu_j\): we can perform too many iterations of the algorithm! In the classical world we’d expect our extra work to reap us some reward (or at least not to make things worse) but here the performance of the algorithm is periodic in \(R\) so we need to stop at exactly the right point in order to find our needle.

+ +

+ We said that it is necessary to apply step 3 some number \(R\) of times in order to obtain the needle state with sufficiently high probability. We’ll now look at what value \(R\) might take. +

+ +

+ First, write our initial state as: + + + \[\ket{\psi} = \nu_0 \ket{x_0} + \sum_{x \neq x_0} \eta_0 \ket{x}\] + + + Where the subscripts on \(\nu\) and \(\eta\) denote the number of Grover iterations performed. Referring to Step 2 of the above, note that \(\nu_0 = \eta_0 = 1/\sqrt{N}\). Now consider the action of the Grover iteration on the \(\nu_j\) and \(\eta_j\): +

+ +
+
    +
  1. + Action of the oracle is denoted by a prime: + + + \[\nu_j^\prime = -\nu_j\] + + +
  2. +
  3. + Inversion about the mean. Call the mean \(\mu\): + + + \[\mu_i = \frac{1}{N}(\nu_i + (N-1)\eta_i) = \frac{\nu_i - \eta_i}{N} + \eta_i\] + + + And so we can see that + + + \[\begin{align} + \nu_{j+1} & = 2\mu_j^\prime - \nu_j^\prime \\ + & = 2\frac{\nu_j^\prime - \eta_j}{N} + 2\eta_j - \nu_j^\prime \\ + & = \frac{N-2}{N} \nu_j + \frac{2(N-1)}{N} \eta_j \\ + \eta_{j+1} & = 2\mu_j^\prime - \eta_j \\ + & = 2\frac{\nu_j^\prime - \eta_j}{N} + 2\eta_j - \eta_j \\ + & = -\frac{2}{N} \nu_j + \frac{N-2}{N} \eta_j + \end{align}\] + +
  4. +
+
+ +

+ It is rather difficult to solve these recurrence relations, but their solutions are given in Ref [3]. The solutions are: + + + \[\begin{align} \nu_j & = \sin([2j+1]\theta) \\ + \eta_j & = \frac{1}{\sqrt{N-1}}\cos([2j+1]\theta) + \end{align}\] + + + Where \(\sin^2 \theta = 1/N\). The validity of these solutions can be proven by induction6 + + + 6. We prove as follows. +
+ Basis case. + Set \(j=0\). Now insert into the solution: + + + \[\begin{align} \nu_0 & = \sin(\theta) \\ + & = \sin(\arcsin(\frac{1}{\sqrt{N}})) \\ + & = \frac{1}{\sqrt{N}} \\ + \eta_0 & = \frac{1}{\sqrt{N-1}}\cos(\theta) \\ + & = \frac{1}{\sqrt{N-1}}\cos(\arcsin(\frac{1}{\sqrt{N}})) \\ + & = \frac{1}{\sqrt{N-1}} \sqrt{1 - \frac{1}{N}} \\ + & = \frac{1}{\sqrt{N-1}} \sqrt{\frac{N-1}{N}} = \frac{1}{\sqrt{N}} + \end{align}\] + + + Which satisfy the boundary conditions we noted in the main text. +
+ + Inductive step. + + Assume the solution is valid for \(j\), now test for \(j+1\): + + + \[\begin{align} \nu_{j+1} & = \sin([2(j+1)+1]\theta) \\ + & = \sin([2j + 1]\theta + 2\theta) \\ + & = \sin([2j + 1]\theta) \cos(2\theta) + \cos([2j + 1]\theta) \sin(2 \theta) \\ + & = \nu_j (\cos^2\theta - \sin^2\theta) + \sqrt{N-1} \eta_j (2 \cos\theta \sin\theta) \\ + & = \nu_j (\frac{N-1}{N} - \frac{1}{N}) + \sqrt{N-1} \eta_j (2 \sqrt{\frac{N-1}{N}} \frac{1}{\sqrt{N}}) \\ + & = \frac{N-2}{N} \nu_j + \frac{2(N-1)}{N} \eta_j \\ + \eta_{j+1} & = \frac{1}{\sqrt{N-1}} \cos([2(j+1) + 1]\theta) \\ + & = \frac{1}{\sqrt{N-1}} \cos([2j + 1]\theta + 2 \theta) \\ + & = \frac{1}{\sqrt{N-1}} \big( \cos([2j+1]\theta) \cos(2\theta) - \sin([2j+1]\theta) \sin(2\theta) \big) \\ + & = \frac{1}{\sqrt{N-1}} ( \sqrt{N-1} \eta_j \frac{N-2}{N} - 2 \nu_j \frac{\sqrt{N-1}}{N} ) \\ + & = -\frac{2}{N} \nu_j + \frac{N-2}{N} \eta_j + \end{align}\] + + + Q.E.D. + +
+ + . As we want to maximise the probability of measuring the needle, we clearly want to maximise \(\nu_j\). The maximum of \(\sin(x)\) is at \(x = \pi/2\) and so we want \((2j + 1)\theta = \pi/2\). Considering that \(j\) must be an integer, we want: + + + \[j = \left \lfloor \frac{\pi - 2\theta}{4\theta} \right \rfloor\] + + + Now see that since \(\sin\theta \approx \theta\) for small \(\theta\) and recalling that \(\sin\theta = 1/\sqrt{N}\): + + + \[j \approx \left \lfloor \frac{\pi - 2/\sqrt{N}}{4/\sqrt{N}} \right \rfloor \approx \left \lfloor \frac{\pi}{4}\sqrt{N} \right \rfloor\] + + + And thus we choose \(R\) to be the above value. Note an interesting - and characteristically quantum - implication of the sinusoidal form for \(\nu_j\): we can perform too many iterations of the algorithm! In the classical world we’d expect our extra work to reap us some reward (or at least not to make things worse) but here the performance of the algorithm is periodic in \(R\) so we need to stop at exactly the right point in order to find our needle. +

+

References

-

[1]L. K. Grover, “Quantum Mechanics Helps in Searching for a Needle in a Haystack,” Phys. Rev. Lett., vol. 79, no. 2, pp. 325–328, Jul. 1997.

-

[2]M. A. Nielsen and I. L. Chuang, Quantum Computation and Quantum Information, 10th Anniversary Edition. Cambridge ; New York: Cambridge University Press, 2010.

-

[3]M. Boyer, G. Brassard, P. Hoeyer, and A. Tapp, “Tight bounds on quantum searching,” Fortschritte der Physik, vol. 46, no. 4-5, pp. 493–505, Jun. 1998.

+

[1] L. K. Grover, “Quantum Mechanics Helps in Searching for a Needle in a Haystack,” Phys. Rev. Lett., vol. 79, no. 2, pp. 325–328, Jul. 1997.

+

[2] M. A. Nielsen and I. L. Chuang, Quantum Computation and Quantum Information, 10th Anniversary Edition. Cambridge ; New York: Cambridge University Press, 2010.

+

[3] M. Boyer, G. Brassard, P. Hoeyer, and A. Tapp, “Tight bounds on quantum searching,” Fortschritte der Physik, vol. 46, no. 4-5, pp. 493–505, Jun. 1998.

+
- + + diff --git a/js/fig1.js b/js/fig1.js index f607bac..24df57f 100644 --- a/js/fig1.js +++ b/js/fig1.js @@ -5,18 +5,12 @@ var y_origin = 40; // What we will use as the origin for the y-axis var w_tip = 35; // Tooltip dimensions var h_tip = 17; +var needle_idx = 7; // Default + var n_circs = Math.pow(2, n_bits); // N var circ_dat = new Array(n_circs).fill(0); var circ_r = 13; -// Helper for drawing straight lines -function myLine(svgHandle, startCoord, endCoord) { - svgHandle.append("line").style("stroke", "gray") - .attr("x1", startCoord[0]).attr("y1", startCoord[1]) - .attr("x2", endCoord[0]).attr("y2", endCoord[1]) - .attr("opacity", 0.25); -} - // Helper for translating groups function gpos(x, y) { return "translate(" + x + "," + y + ")"; @@ -25,10 +19,8 @@ function gpos(x, y) { // Create canvas var svg = d3.select("#fig1").attr("width", w_stack).attr("height", h_stack); -// Draw some "axis" lines -myLine(svg, [0.5, y_origin], [w_stack - 0.5, y_origin]); -myLine(svg, [0.5, y_origin + 4], [0.5, y_origin - 4]); -myLine(svg, [w_stack - 0.5, y_origin + 4], [w_stack - 0.5, y_origin - 4]); +drawMyAxis(w_stack, y_origin, svg, 4); +d3.select("#fig1 > #axis").selectAll("line").style("opacity", 0.25).style("stroke", "gray"); /// Create our 3 main components: the circes, their text, and the tooltip var circs = svg.append("g").attr("id", "circs").selectAll("circle").data(circ_dat).enter().append("circle"); @@ -71,19 +63,20 @@ function circX(i) { // Set up circles circs.attr("cx", function(val, i) { return circX(i); }) .attr("cy", y_origin) - .attr("r", circ_r) - .attr("fill", "black") + .attr("r", function(val, i) { return (i != needle_idx) ? circ_r : (circ_r + 3); }) + .attr("fill", function(val, i) { return (i != needle_idx) ? "black" : "gray"; }) + .classed("chosen-circ", function(val, i) { return i == needle_idx; }) .attr("opacity", 0.85) .on("mouseover", function(val, i) { d3.select(this) .transition() .duration(100) - .attr("cy", y_origin - 8) // Move up + .attr("cy", y_origin - 5) // Move up .attr("r", circ_r + 3); // Grow slightly d3.select(labels[0][i]) .transition() .duration(100) - .attr("y", y_origin - 3); // Move label as well + .attr("y", y_origin); // Move label as well d3.select("#tooltip text").html(dec2bin(i)); // Change tooltip text to binary representation of this bit @@ -110,18 +103,29 @@ circs.attr("cx", function(val, i) { return circX(i); }) .style("opacity", 0); // Hide tooltip again }) .on("click", function(val, i) { // Make the clicked circle grey - needle_idx = i; - d3.select(".chosen-circ") // Un-grey any previously clicked circle - .transition() - .duration(100) - .attr("class", "") - .style("fill", "black") - .attr("r", circ_r); - d3.select(this) - .transition() - .duration(100) - .attr("class", "chosen-circ") - .style("fill", "gray"); + if (last_step <=2) { + needle_idx = i; + d3.select(".chosen-circ") // Un-grey any previously clicked circle + .transition() + .duration(100) + .attr("class", "") + .style("fill", "black") + .attr("r", circ_r); + d3.select(this) + .transition() + .duration(100) + .attr("class", "chosen-circ") + .style("fill", "gray"); + } else { + d3.select(this) + .transition("err") + .duration(100) + .style("fill", "red") + .transition("err") + .delay(200) + .duration(250) + .style("fill", "black"); + } }); labels.text(function(val, i) { return i; }) // Put the labels in the right place diff --git a/js/fig2.js b/js/fig2.js new file mode 100644 index 0000000..35ac2ef --- /dev/null +++ b/js/fig2.js @@ -0,0 +1,275 @@ +var h_algplot = 200; // SVG canvas size +var w_algplot = 580; +var origin_algplot = h_algplot / 2.0; // x-axis position + +var noiseOn = true; // Whether to include noise +var noiseSigma = 0.02; // Strength of noise + +var n_bars = Math.pow(2, n_bits); + +var last_step = 0; // Which step was last performed +var val_R = 0; // How many grover iterations have been performed + +var bar_heights = new Array(n_bars).fill(1.0 / Math.sqrt(n_bars)); // Initialize bars to equal superposition; + + +// Create a scale for taking amplitudes to graphable values +var bar_scale = d3.scale.linear() + .domain([-1.0, 1.0]) + .range([-100, 100]); + +var bar_padding = 12; // Padding between bars +var rect_width = (w_algplot / n_bars) - bar_padding; // Caulculate width of each bar + +var noise = function() { return noiseOn ? d3.random.normal(0.0, noiseSigma)() : 0 }; // Noise function gives normally distributed noise if turned on +bar_heights = bar_heights.map(function(clean) { return clean + noise(); }); // Apply noise to the amplitudes + +var svg = d3.select("svg#fig2").attr("width", w_algplot).attr("height", h_algplot); // Create SVG object +drawMyAxis(w_algplot, origin_algplot, svg, 5); // Draw x-axis +d3.select("#fig2 > #axis").selectAll("line").style("opacity", 0.65).style("stroke", "gray"); + +// Reset button +svg.append("image").attr("id", "resetBtn") + .attr("width", 25) + .attr("height", 25) + .attr("x", w_algplot - 30) + .attr("y", h_algplot - 30) + .attr("xlink:href", "replay.svg") + .attr("opacity",0.3) + .on("mouseover", function() { + d3.select(this).attr("opacity", 0.6); + }) + .on("mouseout", function() { + d3.select(this).attr("opacity", 0.3); + }) + .on("click", function() { reset(); }); + +var rects = svg.append("g").attr("id", "rects") // Add the bars (but don't draw them) + .selectAll("rect") + .data(bar_heights) + .enter() + .append("rect"); + +rects.attr("width", rect_width) // "Draw" bars but with zero height now + .each(function(h) { d3.select(this).attr(makeBar(0)); }) + .attr("x", bar_padding / 2.0) + .classed("graph-rect", true); + +graphScroll().graph(d3.select("#fig2-container")) + .container(d3.select("#steps-container")) + .sections(d3.selectAll("#steps-list > li")); + +function reset() { + bar_heights = new Array(n_bars).fill(1.0 / Math.sqrt(n_bars)); // Initialize bars to equal superposition; + bar_heights = bar_heights.map(function(clean) { return clean + noise(); }); // Apply noise to the amplitudes + rects.data(bar_heights); + rects.each(function(h) { d3.select(this).attr(makeBar(0)); }) + .attr("x", bar_padding / 2.0) + .classed({"graph-rect" : true, + "needle-rect" : function(h,i) { return (i == needle_idx); } }); // Add a class to the needle bar + + last_step = 0; + val_R = 0; + + d3.selectAll(".info-div").style("opacity", 0); + oracleDot.attr("cx", -1.5*r_dot); +} + +/* Helper function for dealing with negative values + * + * SVG uses the top left corner of rectangles and doesn't allow negative heights. + * So, we use |height| but set the y coordinate to either 0 or the "usual" value depending + * on whether the height is negative or positive, respectively. + * +*/ +function makeBar(h) { + return { + "y" : origin_algplot - ((h < 0) ? bar_scale(0) : Math.abs(bar_scale(h))), + "height" : Math.abs(bar_scale(h)) + }; +} + +// Gets probability of measuring the needle to 2 dp +function probMeasure() { + return Math.min(1.00, Math.pow(bar_heights[needle_idx], 2).toFixed(2)); +} + +// Pulses the play button with given ID red +function errPulse(btnID) { + d3.select(btnID) + .transition("err") + .duration(100) + .style("color", "red") + .transition("err") + .delay(200) + .duration(250) + .style("color", "black"); +} + +// First step of the algorithm: \ket{0}^{\otimes n} +function doGroundState() { + if (last_step == 0) { + rects.each(function (h) { + d3.select(this) + .transition("groundstate") + .duration(400) + .ease("quad") + .attr(makeBar(h)); // Give the bars height + }); + last_step = 1; + } else { + errPulse("#groundStateBtn"); + } +} + +// Second step of the algorithm: equal superposition (I know I said I already did that, but now we can see it) +function doHadamard() { + if (last_step == 1) { + rects.transition("hadamard") + .duration(350) + .ease("quad") + .attr("x", function(h, i) { // Space the bars out correctly + return i * (w_algplot / n_bars) + 0.5*bar_padding; + }); + + d3.select("#prob-val").html(probMeasure()) // Update the probability + d3.select("#prob-div").transition() + .duration(150) + .style("opacity", 1); + + last_step = 2; + } else { + errPulse("#hadamardBtn"); + } +} + +// Draw a red dot to symbolize the action of the oracle +var r_dot = 3; +var oracleDot = svg.append("circle") + .attr("cx", -1.5*r_dot) + .attr("cy", origin_algplot) + .attr("r", r_dot) + .style("fill", "red"); + +// First part of grover iteration: flip the phase of the needle +function doOracle() { + if (last_step == 2 || last_step == 4) { + var totalTime = 1300; // How long the oracle pass takes + var scaleFactor = 1.35; // How big to make the needle when we pulse it + + // Show the div with the value of R + if (val_R == 0) { + console.log("here"); + d3.select("#R-val").html(0); + d3.select("#R-div") + .transition() + .duration(150) + .style("opacity", 1); + } + + var dotOffset = 0.5 * w_algplot / n_bars // Starting position of the dot + oracleDot.attr("cx", dotOffset); + oracleDot.transition() // Move the dot + .duration(totalTime) + .ease("sin-in-out") + .attr("cx", w_algplot + r_dot); + + rects.classed("needle-rect", function(h,i) { return (i == needle_idx); }); // Update needle class + + rects.transition("oracle") // This gets complex. Flash along the bars. + .duration(totalTime / n_bars) + .delay(function(h,i) { // Stagger flashes + var easy = d3.ease("sin-out-in"); // Take the easing function, don't fully understand why it needs to be out-in rather than in-out + return easy(i / (n_bars - 1)) * totalTime; + }) + .style("fill", "gray") // The flash colour + .each("start", function(h) { // Called at the start of each flash + thisRec = d3.select(this); + thisNoise = 0.75*noise(); // Oracle-induced noise is weaker, I've decided + + thisRec.transition() // Draw the effect of the noise + .duration(100) + .attr(makeBar(h + thisNoise)); + + thisRec.datum(h + thisNoise); // Update the internal data + + if (thisRec.attr("class").indexOf("needle-rect") == -1) { // If it's not the needle then unflash it + thisRec + .transition() + .delay(120) + .duration(200) + .style("fill", "black"); + } else { // If it is the needle... + thisRec + .transition("pulse") // Pulse it (inflation phase) + .duration(250) + .ease("exp") + .attr("width", scaleFactor*rect_width) + .attr("x", needle_idx * (w_algplot / n_bars) + 0.5*bar_padding + 0.5*rect_width*(1 - scaleFactor)) + .attr(makeBar(scaleFactor*(h + thisNoise))) + .transition("pulse") + .ease("linear") // Pulse it (relaxation phase) + .delay(250 + 20) + .duration(250) + .attr("width", rect_width) + .attr("x", needle_idx * (w_algplot / n_bars) + 0.5*bar_padding) + .attr(makeBar(h + thisNoise)) + .transition("flip") // Flip it down to 0 + .delay(250 + 20 + 250 + 300) + .duration(300) + .ease("cubic-in") + .attr(makeBar(0)) + .transition() // And then past zero + .duration(300) + .ease("cubic-out") + .attr(makeBar(-(h + thisNoise))); + + bar_heights[needle_idx] *= -1; // Update internal data re flip + rects.data(bar_heights); + } + }); + last_step = 3; + } else { + errPulse("#oracleBtn"); + } +} + +// Second part of Grover iteration: inversion about the mean +function doMean() { + if (last_step == 3) { + var mean_height = d3.mean(bar_heights); // Calculate mean amplitude + var mean_y = makeBar(mean_height)["y"]; // And mean coordinate + var meanLine = myLine(svg, [-1, mean_y], [-1, mean_y]); // Start off the mean marking line + meanLine.attr("class", "mean-line"); + + meanLine.transition("meanline") // Draw the line across the graph + .duration(750) + .ease("quad") + .attr("x2", w_algplot + 1); + + bar_heights = bar_heights.map(function(old) { // Perform the actual inversion about the mean + return 2*mean_height - old; + }); + rects.data(bar_heights); // Update internal data + + rects.transition("meanInversion") // Draw the inversion + .duration(1200) + .delay(750 + 300) + .attr("y", function(h) { return makeBar(h)["y"]; }) + .attr("height", function(h) { return makeBar(h)["height"]; }); + + val_R++; + setTimeout(function(){ d3.select("#prob-val").html(probMeasure()); }, 750+300+1200); // Update the measurement probability + setTimeout(function(){ d3.select("#R-val").html(val_R); }, 750+300+1200); // Update number of iterations + + meanLine.transition("disappearLine") // Remove the mean marker + .duration(750) + .delay(750 + 300 + 1200 + 200) + .ease("quad") + .attr("x1", w_algplot + 1); + + last_step = 4; + } else { + errPulse("#meanBtn"); + } +} \ No newline at end of file diff --git a/js/graph-scroll.js b/js/graph-scroll.js new file mode 100644 index 0000000..da5d5ea --- /dev/null +++ b/js/graph-scroll.js @@ -0,0 +1,154 @@ +function graphScroll() { + var windowHeight, + dispatch = d3.dispatch("scroll", "active"), + sections = d3.select('null'), + i = NaN, + sectionPos = [], + n, + graph = d3.select('null'), + isFixed = null, + isBelow = null, + container = d3.select('body'), + containerStart = 0, + belowStart, + eventId = Math.random() + + function reposition(){ + var i1 = 0 + sectionPos.forEach(function(d, i){ + if (d < pageYOffset - containerStart + 200) i1 = i + }) + i1 = Math.min(n - 1, i1) + if (i != i1){ + sections.classed('graph-scroll-active', function(d, i){ return i === i1 }) + + dispatch.active(i1) + + i = i1 + } + + var isBelow1 = pageYOffset > (belowStart - 0.3*window.innerHeight) + if (isBelow != isBelow1){ + isBelow = isBelow1 + graph.classed('graph-scroll-below', isBelow) + } + var isFixed1 = !isBelow && pageYOffset > (containerStart - 0.3*window.innerHeight) + if (isFixed != isFixed1){ + isFixed = isFixed1 + graph.classed('graph-scroll-fixed', isFixed) + } + + // My additions to move the
    of algorithm steps + var containerTopMovey = pageYOffset - containerStart + 0.8*window.innerHeight; + + var moveLeft = 150; + var moveVelocity = 0.8; + + if (containerTopMovey > 0 && moveVelocity*containerTopMovey < moveLeft) { + container.style("margin-left", "-" + moveVelocity*containerTopMovey + "px"); + resize(); + graph.style("opacity", Math.min(moveVelocity*containerTopMovey/(0.75*moveLeft), 1)); + } else if (containerTopMovey > 0) { + graph.style("opacity", 1); + container.style("margin-left", "-" + moveLeft + "px"); + } else if (containerTopMovey < 0) { + container.style("margin-left", null); + graph.style("opacity", 0); + } + } + + function resize(){ + sectionPos = [] + var startPos + sections.each(function(d, i){ + if (!i) startPos = this.getBoundingClientRect().top + sectionPos.push(this.getBoundingClientRect().top - startPos) }) + + var containerBB = container.node().getBoundingClientRect() + var graphBB = graph.node().getBoundingClientRect() + + containerStart = containerBB.top + pageYOffset + belowStart = containerBB.bottom - graphBB.height + pageYOffset + } + + function keydown() { + if (!isFixed) return + var delta + switch (d3.event.keyCode) { + case 39: // right arrow + if (d3.event.metaKey) return + case 40: // down arrow + case 34: // page down + delta = d3.event.metaKey ? Infinity : 1 ;break + case 37: // left arrow + if (d3.event.metaKey) return + case 38: // up arrow + case 33: // page up + delta = d3.event.metaKey ? -Infinity : -1 ;break + case 32: // space + delta = d3.event.shiftKey ? -1 : 1 + ;break + default: return + } + + var i1 = Math.max(0, Math.min(i + delta, n - 1)) + d3.select(document.documentElement) + .interrupt() + .transition() + .duration(500) + .tween("scroll", function() { + var i = d3.interpolateNumber(pageYOffset, sectionPos[i1] + containerStart) + return function(t) { scrollTo(0, i(t)) } + }) + + d3.event.preventDefault() + } + + + var rv ={} + + rv.container = function(_x){ + if (!_x) return container + + container = _x + return rv + } + + rv.graph = function(_x){ + if (!_x) return graph + + graph = _x + return rv + } + + rv.eventId = function(_x){ + if (!_x) return eventId + + eventId = _x + return rv + } + + rv.sections = function (_x){ + if (!_x) return sections + + sections = _x + n = sections.size() + + d3.select(window) + .on('scroll.gscroll' + eventId, reposition) + .on('resize.gscroll' + eventId, resize) + .on('keydown.gscroll' + eventId, keydown) + + resize() + d3.timer(function() { + reposition() + return true + }) + + return rv + } + + d3.rebind(rv, dispatch, "on") + + return rv +} \ No newline at end of file diff --git a/replay.svg b/replay.svg new file mode 100644 index 0000000..eee3f08 --- /dev/null +++ b/replay.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file