Apr 09, 2024 | 4 min read

\( \newcommand{\bra}[1]{\langle #1|} \) \( \newcommand{\ket}[1]{|#1\rangle} \) \( \newcommand{\braket}[2]{\langle #1|#2\rangle} \) \( \newcommand{\i}{{\color{blue} i}} \) \( \newcommand{\Hil}{{\cal H}} \) \( \newcommand{\cg}[1]{{\rm C}#1} \) \( \newcommand{\lp}{\left(} \) \( \newcommand{\rp}{\right)} \) \( \newcommand{\lc}{\left[} \) \( \newcommand{\rc}{\right]} \) \( \newcommand{\lch}{\left\{} \) \( \newcommand{\rch}{\right\}} \) \( \newcommand{\Lp}{\Bigl(} \) \( \newcommand{\Rp}{\Bigr)} \) \( \newcommand{\Lc}{\Bigl[} \) \( \newcommand{\Rc}{\Bigr]} \) \( \newcommand{\Lch}{\Bigl\{} \) \( \newcommand{\Rch}{\Bigr\}} \) \( \newcommand{\rqa}{\quad \Rightarrow \quad} \)

2. Implementación y ejemplos#

from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
import numpy as np
from qiskit_aer import AerSimulator

Para esta implementación se ha seguido la referencia [11]. Para más información sobre la QPE, ver [6]

Fase con expansión binaria exacta#

Vamos a ver un par de ejemplo donde la fase que queremos calcular podemos expandirla de forma exacta en forma binaria usando la notación del punto decimal. Veremos los dos siguientes casos:

\[\begin{split} \theta = \frac{1/4} = 0.01 \\ \theta = \frac{1/8} = 0.001 \end{split}\]

En la sección Fase con expansión binaria no exacta vemos el ejemplo con la fase

\[ 1/3 = 0.010101\ldots \]

Nota (Notación del punto decimal)

Véase que estamos usando la notación del punto decimal:

\[ \frac{1}{4} = 0\cdot2^0 + 0\cdot2^{-1} + 1\cdot2^{-2} = 0.01 \]

Veamos otro ejemplo:

\[ 10.101 = 1 \cdot 2^1 + 0 \cdot 2^0 + 1 \cdot 2^{-1} + 0 \cdot 2^{-2} + 1 \cdot 2^{-3} = 2 + \frac{1}{2}+\frac{1}{8} \]

Debemos tener el cuenta también que multiplicar por 2 implica mover la coma un ligar a la izquierda:

\[\begin{split} 2 \cdot 10.101 = 101.01 \\ 2^2 \cdot 10.101 = 1010.1 \end{split}\]

Ejemplo 1: con la puerta S#

A modo de ejemplo vamos a realizar la IPE en la compuerta \(S\) de un solo qubit. La compuerta \(S\) viene dada por la matriz

\[\begin{split} S = \begin{pmatrix} 1 & 0\\ 0 & e^{i\pi / 2} \end{pmatrix}\end{split}\]

Usaremos el estado propio \(|\psi\rangle = |1\rangle\), que tiene un valor propio \(e^{i\pi / 2}= e^{i2\pi \cdot 1/4}\). Entonces tenemos \(\theta = 1/4 = 0.01 = 0.\theta_1 \theta_2\). Dado que \(\theta\) se puede representar exactamente con 2 bits, nuestra implementación de circuito cuántico utilizará un registro clásico con dos bits para almacenar el resultado.

Paso 1#

En el primer paso del algoritmo, medimos el bit menos significativo de \(\theta\).


def step_1_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:
    # qr is a quantum register with 2 qubits
    # cr is a classical register with 2 bits

    qc = QuantumCircuit(qr, cr)
   
    # Inicializacion
    qc.h(qr[0])
    qc.x(qr[1])
    
    qc.cp(np.pi/2, qr[0], qr[1])
    qc.cp(np.pi/2, qr[0], qr[1])    
    
    qc.h(qr[0])
    
    qc.measure(qr[0], cr[0])

    return qc


qr_qk = QuantumRegister(2, "q")
cr_qk = ClassicalRegister(2, "c")

qc_qk = step_1_circuit(qr_qk, cr_qk)
qc_qk.draw("mpl")

/home/dcb/Programs/miniconda/miniconda3/envs/qiskit_qibo_penny_2/lib/python3.11/site-packages/qiskit/visualization/circuit/matplotlib.py:266: FutureWarning: The default matplotlib drawer scheme will be changed to "iqp" in a following release. To silence this warning, specify the current default explicitly as style="clifford", or the new default as style="iqp".
  self._style, def_font_ratio = load_style(self._style)
../../../_images/8a6dfb86ce963df4c524c59efa39a0793dbe27a89c1377017493b61ddd2722aa.png

Paso 2#

En el primer paso, medimos el bit menos significativo \(\theta_2\). En el segundo (y último) paso, extraemos el siguiente bit \(\theta_1\), que implicará aplicar una corrección de fase para cancelar la contribución de fase de \(\theta_2\). La corrección de fase depende del valor del registro clásico que contiene \(\theta_2\).

# En Qiskit necesitamos los Dinamic Circuits para realizar esta retroalimentación clásica

def step_2_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:
    # qr is a quantum register with 2 qubits
    # cr is a classical register with 2 bits

    # begin with the circuit from Step 1
    qc = step_1_circuit(qr, cr)
    
    qc.reset(qr[0])
    
    qc.h(qr[0])
    
    with qc.if_test((cr[0],1)):
        qc.p(-2*np.pi/4, qr[0])
    
    
    qc.cp(np.pi/2, qr[0], qr[1])    
    
    qc.h(qr[0])
    
    qc.measure(qr[0], cr[1])
    

    return qc


qr_qk = QuantumRegister(2, "q")
cr_qk = ClassicalRegister(2, "c")

qc_qk = step_2_circuit(qr_qk, cr_qk)
qc_qk.draw("mpl")

../../../_images/3f4e46b7df612501646b0b978a070998563e6d8139418b840f319b99aa69ba01.png

Ejecutar la simulación#

Vamos a ejecutarlo en un simulador local.

sim_qk = AerSimulator()
job_qk = sim_qk.run(qc_qk, shots=1000)
result_qk = job_qk.result()
counts_qk = result_qk.get_counts()
counts_qk

{'01': 1000}

Ejemplo 2: con la puerta T#

Vamos ahora a contruir el circuito para implementar la IPE para \(U=T\):

\[\begin{split} T = \begin{pmatrix} 1 & 0\\ 0 & e^{i\pi / 4} \end{pmatrix}\end{split}\]
def t_gate_ipe_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:
    # qr is a quantum register with 2 qubits
    # cr is a classical register with 3 bits
    
    qc = QuantumCircuit(qr, cr)
    
    qc.x(qr[1])

    for i in range(3):
    
        if i > 0:
            qc.reset(qr[0])
            
        qc.h(qr[0])
        for j in range(i):
            with qc.if_test((cr[j],1)):
                qc.p(-2**(j)*np.pi/2**(i), qr[0])

        for j in range(2**(3-1-i)):
            qc.cp(np.pi/4, qr[0], qr[1]) 
        qc.h(qr[0])
        qc.measure(qr[0], cr[i])
    

    return qc


qr_qk = QuantumRegister(2, "q")
cr_qk = ClassicalRegister(3, "c")

qc_qk = t_gate_ipe_circuit(qr_qk, cr_qk)
qc_qk.draw("mpl")

/home/dcb/Programs/miniconda/miniconda3/envs/qiskit_qibo_penny_2/lib/python3.11/site-packages/qiskit/visualization/circuit/matplotlib.py:266: FutureWarning: The default matplotlib drawer scheme will be changed to "iqp" in a following release. To silence this warning, specify the current default explicitly as style="clifford", or the new default as style="iqp".
  self._style, def_font_ratio = load_style(self._style)
../../../_images/6d8ec1f2f3951d77d11c8f24201b47f0e5477083b1da06b2df598a3ad88f2875.png

Hacemos la simulación:

sim_qk = AerSimulator()
job_qk = sim_qk.run(qc_qk, shots=1000)
result_qk = job_qk.result()
counts_qk = result_qk.get_counts()
counts_qk

{'001': 1000}

Fase con expansión binaria no exacta#

Consideremos el caso cuando la fase no tiene una expansión binaria exacta, por ejemplo, \(\varphi = 1/3\). En este caso, la compuerta de un solo qubit tiene la matriz unitaria

\[\begin{split} U = \begin{pmatrix} 1 & 0\\ 0 & e^{i2\pi / 3} \end{pmatrix} \end{split}\]

El ángulo \(\varphi = 1/3\) no tiene una expansión binaria finita exacta. Por el contrario, tiene la expansión binaria infinita

\[ 1/3 = 0.010101\ldots \]

En la práctica trabajamos con un número fijo de bits de precisión, por lo que nuestro objetivo es obtener el valor más cercano que se pueda representar con esos bits. En el siguiente ejemplo, usaremos dos bits de precisión. En este caso, el valor más cercano es \(0.01 = 1/4\). Debido a que este valor no representa la fase exacta, existe cierta probabilidad de que obtengamos un resultado diferente y menos preciso.

def u_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:
    # qr is a quantum register with 2 qubits
    # cr is a classical register with 2 bits

    qc = QuantumCircuit(qr, cr)

    # Initialization
    q0, q1 = qr
    qc.h(q0)
    qc.x(q1)

    # Apply control-U operator as many times as needed to get the least significant phase bit
    u_angle = np.pi / 3
    k = 1
    cphase_angle = u_angle * 2**k
    qc.cp(cphase_angle, q0, q1)

    # Measure the auxiliary qubit in x-basis into the first classical bit
    qc.h(q0)
    c0, c1 = cr
    qc.measure(q0, c0)

    # Reset and re-initialize the auxiliary qubit
    qc.reset(q0)
    qc.h(q0)

    # Apply phase correction conditioned on the first classical bit
    with qc.if_test((c0, 1)):
        qc.p(-np.pi / 2, q0)

    # Apply control-U operator as many times as needed to get the next phase bit
    k = 0
    cphase_angle = u_angle * 2**k
    qc.cp(cphase_angle, q0, q1)

    # Measure the auxiliary qubit in x-basis into the second classical bit
    qc.h(q0)
    qc.measure(q0, c1)

    return qc


qr_qk = QuantumRegister(2, "q")
cr_qk = ClassicalRegister(2, "c")
qc_qk = QuantumCircuit(qr_qk, cr_qk)
qc_qk = u_circuit(qr_qk, cr_qk)
qc_qk.draw("mpl")

../../../_images/9fb2c5c32965fd27f5d19541dbb340741ea1d02eb5d0e978b780091b1835df57.png

Simulemos

from qiskit_aer import AerSimulator

sim_qk = AerSimulator()
job_qk = sim_qk.run(qc_qk, shots=1000)
result_qk = job_qk.result()
counts_qk = result_qk.get_counts()
print(counts_qk)
success_probability_qk = counts_qk["01"] / counts_qk.shots()
print(f"Success probability: {success_probability_qk}")

{'11': 45, '10': 74, '00': 192, '01': 689}
Success probability: 0.689

Como puedes ver, esta vez, no tenemos garatía de obtener el resultado deseado. Una pregunta natural es: ¿Cómo podemos aumentar la probabilidad de éxito?

Una forma en que el algoritmo falla es que el primer bit medido es incorrecto. En este caso, la corrección de fase aplicada antes de medir el segundo bit también es incorrecta, lo que hace que el resto de los bits también sean incorrectos. Una forma sencilla de mitigar este problema es repetir la medición de los primeros bits varias veces y obtener un voto mayoritario para aumentar la probabilidad de que midamos el bit correctamente. La implementación de este procedimiento dentro de un solo circuito requiere realizar aritmética en los resultados medidos. Debido a una limitación temporal en Qiskit, actualmente no es posible realizar operaciones aritméticas en bits medidos y condicionar futuras operaciones de circuito en los resultados. Entonces, aquí mediremos cada bit usando circuitos separados.

Las siguientes celdas de código construyen y simulan un circuito IPE para medir solo el primer bit de la fase.

def u_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:
    # qr is a quantum register with 2 qubits
    # cr is a classical register with 1 bits

    qc = QuantumCircuit(qr, cr)

    # Initialization
    q0, q1 = qr
    qc.h(q0)
    qc.x(q1)

    # Apply control-U operator as many times as needed to get the least significant phase bit
    u_angle = np.pi / 3
    k = 1
    cphase_angle = u_angle * 2**k
    qc.cp(cphase_angle, q0, q1)

    # Measure the auxiliary qubit in x-basis
    qc.h(q0)
    (c0,) = cr
    qc.measure(q0, c0)

    return qc


qr_qk = QuantumRegister(2, "q")
cr_qk = ClassicalRegister(1, "c")

qc_qk = u_circuit(qr_qk, cr_qk)
qc_qk.draw("mpl")

../../../_images/70641c143cac4d5cea630ac6c5e50d03bedc1b215c373c21a34cc91f3ecc8558.png

Simulemos

job_qk = sim_qk.run(qc_qk, shots=1000)
result_qk = job_qk.result()
counts_qk = result_qk.get_counts()
print(counts_qk)

{'0': 249, '1': 751}

Con suerte, el bit correcto se midió la mayoría de las veces. Veamos cual fue el más medido:


keys_qk   = counts_qk.keys()
values_qk = counts_qk.values()

zip_list_qk = zip(keys_qk, values_qk)
zip_sorted_qk = list(sorted(zip_list_qk, key = lambda x: -x[1]))
keys_qk, values_qk = zip(*list(zip_sorted_qk))

step1_bit_qk = eval(keys_qk[0])

print(step1_bit_qk)

1

Ahora construyamos el circuito para medir el segundo bit de la fase. Reemplazemos la primera etapa del circuito con una que simplemente establezca el bit auxiliar en el valor que medimos anteriormente, de modo que siempre midamos el valor correcto para el primer bit de la fase.

def u_circuit(qr: QuantumRegister, cr: ClassicalRegister, step1_bit : int) -> QuantumCircuit:
    # qr is a quantum register with 2 qubits
    # cr is a classical register with 2 bits

    qc = QuantumCircuit(qr, cr)
    
    if step1_bit == 1:
        qc.x(qr[0])
        qc.measure(qr[0], cr[0])
        qc.x(qr[0])
    else:
        qc.measure(qr[0], cr[0])

    
    qc.h(qr[0])
    qc.x(qr[1])

    with qc.if_test((cr[0], 1)):
        qc.p(-np.pi/2, qr[0])
        
    # Apply control-U operator as many times as needed to get the least significant phase bit
    u_angle = np.pi / 3
    k = 0
    cphase_angle = u_angle * 2**k
    qc.cp(cphase_angle, qr[0], qr[1])

    # Measure the auxiliary qubit in x-basis
    qc.h(qr[0])
    qc.measure(qr[0], cr[1])

    return qc


qr_qk = QuantumRegister(2, "q")
cr_qk = ClassicalRegister(2, "c")

qc_qk = u_circuit(qr_qk, cr_qk, step1_bit_qk)
qc_qk.draw("mpl")

../../../_images/f551626825b3e197dd632be9f054181ee335e833573771fc80529cd4e5c3eda5.png

Simulemos

sim_qk = AerSimulator()
job_qk = sim_qk.run(qc_qk, shots=1000)
result_qk = job_qk.result()
counts_qk = result_qk.get_counts()
print(counts_qk)
success_probability_qk = counts_qk["01"] / counts_qk.shots()
print(f"Success probability: {success_probability_qk}")

{'11': 62, '01': 938}
Success probability: 0.938

Nota (Precisión)

Vemos que el resultado sigue sin ser muy bueno, pero es el que esperabamos \(1/4\). Podemos mejorar la precisión de este valor aumentando el número de qúbit que usamos, de dos a tres, cuatro,…


Autores:

David Castaño (UMA-SCBI), Raul Fuentes (BSC-CNS), Daniel Talaván (COMPUTAEX), Francisco Matanza (UNICAN)

../../../_images/Logo_UMA1.jpeg ../../../_images/BSC-blue-medium1.png ../../../_images/COMPUTAEX1.jpg ../../../_images/Logo_UNICAM1.jpg
https://quantumspain-project.es/wp-content/uploads/2022/11/Logo_QS_EspanaDigital.png
Licencia Creative Commons

License: Licencia Creative Commons Atribución-CompartirIgual 4.0 Internacional.

This work has been financially supported by the Ministry for Digital Transformation and of Civil Service of the Spanish Government through the QUANTUM ENIA project call - Quantum Spain project, and by the European Union through the Recovery, Transformation and Resilience Plan - NextGenerationEU within the framework of the Digital Spain 2026 Agenda.