# 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