Source code for qolumbina.programs.quantum_adder.draper_adder

# This code is developed through cross-language conversion from 
# https://github.com/MgcosA/Code_of_Testing_Oracle_Quantum_Program_Article/blob/master/qolumbina/programs/QAdder.qs
# 
# In detail, the raw program is written in Q#, and we rewrite it in Qiskit.

import numpy as np
from qiskit import QuantumCircuit, QuantumRegister
from qolumbina.programs.quantum_fourier_transform import QFT


# ---------- benchmark registration ----------
from ..benchmark_registry import register_benchmark
from pathlib import Path
@register_benchmark(
    Path(__file__).stem,
    family=Path(__file__).resolve().parent.name, 
    description="Quantum adder (Draper Adder)",
    class_name="DraperAdder",
    source={
        "repo": "https://github.com/MgcosA/Code_of_Testing_Oracle_Quantum_Program_Article/blob/master/",
        "file": "qolumbina/programs/QAdder.qs",
        "sdk": "Q#",
        "available_doc": True
    },
    testability_refactoring=[
        "Cross-language translation", 
        "Structure reorganization"
    ] 
)
def create_draper_adder(input_qubits):
    return DraperAdder(input_qubits=input_qubits)


[docs] class DraperAdder(QuantumCircuit): # ---------- init ---------- # We do not consider QAdder(qx : Qubit[], qy : Qubit[]) in the Q# version, # because it is not convenient to specify the number of qubits in testing, # where we do not expect qx and qy to have different lengths. r""" QFT-based quantum adder (Draper Adder), where addition is modulo 2^n. """ def __init__(self, input_qubits: int, name: str | None = None): r""" Args: input_qubits: The number of qubits in each of the two addends, i.e., :math:`n` for both addends :math:`\ket{x}_n` and :math:`\ket{y}_n`. name: Optional name of the circuit. """ self._input_qubits = input_qubits # ---------- registers ---------- qx = QuantumRegister(input_qubits, "x") qy = QuantumRegister(input_qubits, "y") # qy is more significant than qx # little-endian ordering: |y_(n-1) ... y_1 y_0>|x_(n-1) ... x_1 x_0> super().__init__(qx, qy, name=name or "QuantumAdder") self._build()
[docs] def CRk(self, qc: QuantumCircuit, qctrl: int, k: int, qtarget: int) -> None: """ Controlled R1 rotation: theta = 2*pi / 2^k """ theta = 2.0 * np.pi / (2 ** k) qc.cp(theta, qctrl, qtarget)
[docs] def Reverse(self, qc: QuantumCircuit, qs: list[int]) -> None: """ Reverse the order of qubits in qs. """ n = len(qs) for i in range(n // 2): qc.swap(qs[i], qs[n - i - 1])
[docs] def ApplyQFT(self, qc: QuantumCircuit, qs: list[int]) -> None: """ Apply QFT on the qubits in qs without the final swap layer. Remember that QFT is the one included in our repository. """ n = len(qs) qft_circuit = QFT(num_qubits=n, do_swaps=False, inverse=False) # qc.append(qft_circuit.to_gate(), [qs[i] for i in range(n)]) qc.compose(qft_circuit, qubits=[qs[i] for i in range(n)], inplace=True)
[docs] def ApplyIQFT(self, qc: QuantumCircuit, qs: list[int]) -> None: """ Apply inverse QFT on the qubits in qs without the final swap layer. Remember that QFT is the one included in our repository. """ n = len(qs) iqft_circuit = QFT(num_qubits=n, do_swaps=False, inverse=True) # qc.append(iqft_circuit.to_gate(), [qs[i] for i in range(n)]) qc.compose(iqft_circuit, qubits=[qs[i] for i in range(n)], inplace=True)
# ---------- build ---------- def _build(self) -> None: qx = list(range(0, self._input_qubits)) qy = list(range(self._input_qubits, 2 * self._input_qubits)) N = self._input_qubits # Implement the within-apply structure, whereas it is not included in Qiskit. # within {A} apply {B} -> A; B; A^{dagger} # Operation A self.ApplyQFT(self, qy) # Do not include the final swap layer self.Reverse(self, qy) # Operation B for i in range(N): for j in range(N - i): self.CRk(self, qx[j], N - i - j, qy[i]) # Operation A^{dagger} for uncomputation self.Reverse(self, qy) # Reverse back, where Reverse is self-inverse self.ApplyIQFT(self, qy) # Inverse QFT