Source code for qolumbina.programs.quantum_adder.weighted_adder

# The original version of the following code is sourced from Qiskit circuit library:
# https://github.com/Qiskit/qiskit/blob/stable/2.3/qiskit/circuit/library/arithmetic/weighted_adder.py#L26-L357
#
# Original repository link:
# https://github.com/Qiskit/qiskit/tree/f14e0b29a484795034447ea5bfb637fe845c194f
#
# This program is adapted for use as a benchmark in controlled software testing experiments.
# Modifications made to the original code include (for Apache License 2.0):
# - Refactored to unify the inherent class as QuantumCircuit
# - Modify the input port
#
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Compute the weighted sum of qubit states."""

from __future__ import annotations

import warnings
from typing import List, Optional
import numpy as np

from qiskit.circuit import QuantumRegister, AncillaRegister, QuantumCircuit 
import warnings

warnings.filterwarnings(
    "ignore",
    category=DeprecationWarning,
    message=".*mcx.*"
)

# ---------- benchmark registration ----------
from ..benchmark_registry import register_benchmark
from pathlib import Path
@register_benchmark(
    Path(__file__).stem,
    family=Path(__file__).resolve().parent.name,
    description="Weighted adder circuit",
    class_name="WeightedAdder",
    source={
        "repo": "https://github.com/Qiskit/qiskit/tree/f14e0b29a484795034447ea5bfb637fe845c194f",
        "file": "qiskit/circuit/library/arithmetic/weighted_adder.py",
        "sdk": "Qiskit",
        "available_doc": True
    },
    testability_refactoring=[
        "Structure reorganization"
    ]     
)
def create_weighted_adder(input_qubits, weights):
    return WeightedAdder(num_state_qubits=input_qubits, weights=weights)

[docs] class WeightedAdder(QuantumCircuit): r""" A circuit to compute the weighted sum of qubit registers. """ def __init__( self, num_state_qubits: int, # Optional[int] = None -> int weights: Optional[List[int]] = None, name: str = "adder" ) -> None: r""" Args: num_state_qubits: The number of state qubits. weights: list of non-negative integer weights :math:`\lambda_{0},\cdots,\lambda_{n-1}`. If `None`, all weights are set to 1. name: The name of the circuit. Raises: ValueError: If `weights` is not `None` and contains non-integer values. """ if weights: for i, weight in enumerate(weights): if not np.isclose(weight, np.round(weight)): raise ValueError("Non-integer weights are not supported!") weights[i] = np.round(weight) super().__init__(name=name) self._weights = weights self._num_state_qubits = num_state_qubits self._reset_registers() self._build() @property def num_sum_qubits(self) -> int: """The number of sum qubits in the circuit. Returns: The number of qubits needed to represent the weighted sum of the qubits. """ if sum(self.weights) > 0: return int(np.floor(np.log2(sum(self.weights))) + 1) return 1 @property def weights(self) -> List[int]: """The weights for the qubit states. Returns: The weight for the qubit states. """ if self._weights: return self._weights if self.num_state_qubits: return [1] * self.num_state_qubits # Code should not reach here # return None raise RuntimeError("Weights are not set and number of state qubits is None.") @property def num_state_qubits(self) -> int: """The number of qubits to be summed. Returns: The number of state qubits. """ return self._num_state_qubits def _reset_registers(self): """Reset the registers.""" self.qregs = [] if self.num_state_qubits: qr_state = QuantumRegister(self.num_state_qubits, name="state") qr_sum = QuantumRegister(self.num_sum_qubits, name="sum") self.qregs = [qr_state, qr_sum] if self.num_carry_qubits > 0: qr_carry = AncillaRegister(self.num_carry_qubits, name="carry") self.add_register(qr_carry) if self.num_control_qubits > 0: qr_control = AncillaRegister(self.num_control_qubits, name="control") self.add_register(qr_control) @property def num_carry_qubits(self) -> int: """The number of carry qubits required to compute the sum. Note that this is not necessarily equal to the number of ancilla qubits, these can be queried using ``num_ancilla_qubits``. Returns: The number of carry qubits required to compute the sum. """ return self.num_sum_qubits - 1 @property def num_control_qubits(self) -> int: """The number of additional control qubits required. Note that the total number of ancilla qubits can be obtained by calling the method ``num_ancilla_qubits``. Returns: The number of additional control qubits required (0 or 1). """ return int(self.num_sum_qubits > 2) def _check_configuration(self, raise_on_failure=True): """Check if the current configuration is valid.""" valid = True if self._num_state_qubits is None: valid = False if raise_on_failure: raise AttributeError("The number of state qubits has not been set.") if self._num_state_qubits != len(self.weights): valid = False if raise_on_failure: raise ValueError("Mismatching number of state qubits and weights.") return valid def _build(self): """Build the weighted adder circuit.""" num_result_qubits = self.num_state_qubits + self.num_sum_qubits circuit = QuantumCircuit(*self.qregs) qr_state = circuit.qubits[: self.num_state_qubits] qr_sum = circuit.qubits[self.num_state_qubits : num_result_qubits] qr_carry = circuit.qubits[num_result_qubits : num_result_qubits + self.num_carry_qubits] qr_control = circuit.qubits[num_result_qubits + self.num_carry_qubits :] # loop over state qubits and corresponding weights for i, weight in enumerate(self.weights): # only act if non-trivial weight if np.isclose(weight, 0): continue # get state control qubit q_state = qr_state[i] # get bit representation of current weight weight_binary = f"{int(weight):b}".rjust(self.num_sum_qubits, "0")[::-1] # loop over bits of current weight and add them to sum and carry registers for j, bit in enumerate(weight_binary): if bit == "1": if self.num_sum_qubits == 1: circuit.cx(q_state, qr_sum[j]) elif j == 0: # compute (q_sum[0] + 1) into (q_sum[0], q_carry[0]) # - controlled by q_state[i] circuit.ccx(q_state, qr_sum[j], qr_carry[j]) circuit.cx(q_state, qr_sum[j]) elif j == self.num_sum_qubits - 1: # compute (q_sum[j] + q_carry[j-1] + 1) into (q_sum[j]) # - controlled by q_state[i] / last qubit, # no carry needed by construction circuit.cx(q_state, qr_sum[j]) circuit.ccx(q_state, qr_carry[j - 1], qr_sum[j]) else: # compute (q_sum[j] + q_carry[j-1] + 1) into (q_sum[j], q_carry[j]) # - controlled by q_state[i] circuit.x(qr_sum[j]) circuit.x(qr_carry[j - 1]) with warnings.catch_warnings(): warnings.filterwarnings( "ignore", category=DeprecationWarning, module="qiskit" ) circuit.mcx( [q_state, qr_sum[j], qr_carry[j - 1]], qr_carry[j], qr_control, mode="v-chain", ) circuit.cx(q_state, qr_carry[j]) circuit.x(qr_sum[j]) circuit.x(qr_carry[j - 1]) circuit.cx(q_state, qr_sum[j]) circuit.ccx(q_state, qr_carry[j - 1], qr_sum[j]) else: if self.num_sum_qubits == 1: pass # nothing to do, since nothing to add elif j == 0: pass # nothing to do, since nothing to add elif j == self.num_sum_qubits - 1: # compute (q_sum[j] + q_carry[j-1]) into (q_sum[j]) # - controlled by q_state[i] / last qubit, # no carry needed by construction circuit.ccx(q_state, qr_carry[j - 1], qr_sum[j]) else: # compute (q_sum[j] + q_carry[j-1]) into (q_sum[j], q_carry[j]) # - controlled by q_state[i] with warnings.catch_warnings(): warnings.filterwarnings( "ignore", category=DeprecationWarning, module="qiskit" ) circuit.mcx( [q_state, qr_sum[j], qr_carry[j - 1]], qr_carry[j], qr_control, mode="v-chain", ) circuit.ccx(q_state, qr_carry[j - 1], qr_sum[j]) # uncompute carry qubits for j in reversed(range(len(weight_binary))): bit = weight_binary[j] if bit == "1": if self.num_sum_qubits == 1: pass elif j == 0: circuit.x(qr_sum[j]) circuit.ccx(q_state, qr_sum[j], qr_carry[j]) circuit.x(qr_sum[j]) elif j == self.num_sum_qubits - 1: pass else: circuit.x(qr_carry[j - 1]) with warnings.catch_warnings(): warnings.filterwarnings( "ignore", category=DeprecationWarning, module="qiskit" ) circuit.mcx( [q_state, qr_sum[j], qr_carry[j - 1]], qr_carry[j], qr_control, mode="v-chain", ) circuit.cx(q_state, qr_carry[j]) circuit.x(qr_carry[j - 1]) else: if self.num_sum_qubits == 1: pass elif j == 0: pass elif j == self.num_sum_qubits - 1: pass else: # compute (q_sum[j] + q_carry[j-1]) into (q_sum[j], q_carry[j]) # - controlled by q_state[i] circuit.x(qr_sum[j]) with warnings.catch_warnings(): warnings.filterwarnings( "ignore", category=DeprecationWarning, module="qiskit" ) circuit.mcx( [q_state, qr_sum[j], qr_carry[j - 1]], qr_carry[j], qr_control, mode="v-chain", ) circuit.x(qr_sum[j]) self.append(circuit.to_gate(), self.qubits)