Source code for qrisp.alg_primitives.arithmetic.adders.ripple_carry_adder

"""
********************************************************************************
* Copyright (c) 2025 the Qrisp authors
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************
"""

import numpy as np

from qrisp import QuantumCircuit
from qrisp.misc import gate_wrap

# Implementation of the modular Cuccaro-Adder https://arxiv.org/pdf/quant-ph/0410184.pdf
# Has complexity O(log(N)) (Fourier-Adding has O(log(N)^2))


def MAJ_gate():
    qc = QuantumCircuit(3)

    qc.cx(2, 1)
    qc.cx(2, 0)
    qc.mcx([0, 1], 2)

    result = qc.to_gate()

    result.name = "MAJ"

    return result


def UMA_gate(mode=2):
    qc = QuantumCircuit(3)
    if mode == 2:
        qc.mcx([0, 1], 2)
        qc.cx(2, 0)
        qc.cx(0, 1)

    if mode == 3:
        qc.x(1)
        qc.cx(0, 1)
        qc.mcx([0, 1], 2)
        qc.x(1)
        qc.cx(2, 0)
        qc.cx(2, 1)

    result = qc.to_gate()
    result.name = "UMA"
    return result


# Performs inplace addition on qv2, ie.
# qv1 += qv2


def cuccaro_procedure(qs, qubit_list_1, qubit_list_2, output_qubit=None, carry_in=None):
    if len(qubit_list_1) != len(qubit_list_2):
        raise Exception(
            "Tried to call Cuccaro-procedure with qubit lists of unequal length"
        )

    from qrisp.core import QuantumVariable

    if carry_in is None:
        # Request ancilla Qubit
        ancilla = QuantumVariable(1)
    else:
        ancilla = [carry_in]

    # Prepare MAJ/UMA gate qubits
    slot_1_qbs = list(ancilla) + qubit_list_1[:-1]
    slot_2_qbs = qubit_list_2
    slot_3_qbs = qubit_list_1

    iterations = len(slot_1_qbs)

    # Perform 1st step of the modular addition section
    for i in range(iterations):
        qbits = [slot_1_qbs[i], slot_2_qbs[i], slot_3_qbs[i]]
        qs.append(MAJ_gate(), qbits)

    # Calculate output qbit
    if output_qubit:
        qs.cx(qbits[2], output_qubit)

    # Perform UMA iterations
    for i in range(iterations - 1, -1, -1):
        qbits = [slot_1_qbs[i], slot_2_qbs[i], slot_3_qbs[i]]

        qs.append(UMA_gate(), qbits)

    if carry_in is None:
        # Detete ancilla
        ancilla.delete()


# Wrapper for the Cuccaro procedure
# This function mainly serves to determine the input qubits of the Cucarro procedure
# depending on the shape of the input QuantumFloats
# The effect of this function is
# qv1 += qv2


[docs] @gate_wrap(is_qfree=True, permeability=[1]) def inpl_add( qf1, qf2, ignore_rounding_error=False, ignore_overflow_error=False, adder="thapliyal", ): """ Performs in-place addition of the second argument onto the first. In Python syntax: :: qf1 += qf2 There are two different algorithms available: The `Thapliyal adder <https://arxiv.org/abs/1712.02630>`_ and the `Cuccaro adder <https://arxiv.org/abs/quant-ph/0410184>`_ Parameters ---------- qf1 : QuantumFloat The QuantumFloat that will be in-place modified. qf2 : QuantumFloat The QuantumFloat that is being added. ignore_rounding_error : bool, optional If set to False, an Exception will be raised if qf2 has higher precision than qf1. The default is False. ignore_overflow_error : bool, optional If set to False, an Exception will be raised if qf2 has higher maximum significance than qf2. The default is False. adder : str, optional Specifies the adder. Available are "thapliyal" and "cuccaro". The default is "thapliyal". Raises ------ Exception Tried to add signed QuantumFloat onto non signed QuantumFloat. Examples -------- We create two QuantumFloats and apply the inplace adder >>> from qrisp import QuantumFloat, inpl_add >>> qf_0 = QuantumFloat(5) >>> qf_1 = QuantumFloat(5) >>> qf_0[:] = 4 >>> qf_1[:] = 3 >>> inpl_add(qf_0, qf_1) >>> print(qf_0) {7.0: 1.0} """ qs = qf1.qs # If qf2 has lower exponent, the qf2 bits with less significance than all of qf1 # can not be added to qf1 (rounding error) if not ignore_rounding_error and qf1.exponent > qf2.exponent: raise Exception( "Tried to add QuantumFloat to QuantumFloat of lower precision" " (set ignore_rounding_error = True)" ) # If qf2 has higher maximum significance than qf1, the qf2 bits with higher # significance than all of qf1 can not be added to qf1 (overflow error) if not ignore_overflow_error and qf1.mshape[1] < qf2.mshape[1]: raise Exception( "Tried to add QuantumFloat to QuantumFloat of lower precision" " (set ignore_overflow_error = True)" ) # Determine the significance range # For instance for a QuantumFloat with datashape [-2,3] # has significance range [-2,-1,0,1,2,3] significance_range_qf1 = list(range(qf1.mshape[0], qf1.mshape[1] + 1)) significance_range_qf2 = list(range(qf2.mshape[0], qf2.mshape[1] + 1)) # Determine the intersection of the significance ranges signficance_range_intersetion = list( set(significance_range_qf1).intersection(significance_range_qf2) ) # Determine maximum and minimum significance of the addition # The maximum significance is the maximum significance of qf1 # (since we are adding in-place) # The minimum significance is the minimum of the intersection because # if qf2.exponent < qf1.exponent # we are allowed to ignore possible rounding errors # (skip all significances lower than qf1.exponent) # if qf2.exponent > qf1.exponent # The qbits of qf1 with lower significance than all of qf2 # stay unchanged during addition max_sig = max(significance_range_qf1) min_sig = min(signficance_range_intersetion) # If the maximum significance is higher than the maximum signficance of qf2, we need # to augment some ancilla qubits because the Cuccaro-procedure requires equal # amount of input qubits from qrisp.core import QuantumVariable if max_sig > max(significance_range_qf2): # print(max_sig-max(significance_range_qf2)) ancilla_var = QuantumVariable( max_sig - max(significance_range_qf2) - int(qf2.signed) ) augmented_qf2_qbs = qf2.reg + ancilla_var.reg else: augmented_qf2_qbs = qf2.reg # Determine the bit window of which bits should participate in the Cuccaro-procedure bit_window_1 = [ significance_range_qf1.index(min_sig), significance_range_qf1.index(max_sig), ] if max_sig > max(significance_range_qf2): bit_window_2 = [significance_range_qf2.index(min_sig), len(augmented_qf2_qbs)] else: bit_window_2 = [ significance_range_qf2.index(min_sig), significance_range_qf2.index(max_sig), ] # print(max_sig) # print(ancilla_var_.size) # print(significance_range_qf1) # print(significance_range_qf2) # print(bit_window_1) # print(bit_window_2) # Determine qubit lists qubit_list_1 = qf1.reg[bit_window_1[0] : bit_window_1[1]] qubit_list_2 = augmented_qf2_qbs[bit_window_2[0] : bit_window_2[1]] # print(len(qubit_list_1)) # print(len(qubit_list_2)) # Now we treat the sign bits if qf2.signed and not qf1.signed: raise Exception("Tried to add signed QuantumFloat to non signed QuantumFloat") # The signs are handled via modular arithmetic # According to the Cuccaro-Paper this is done via manipulating # the final qubits of the input list # if True: if qf1.signed: qubit_list_1.append(qf1[-1]) if qf2.signed: qubit_list_2.append(qf2[-1]) else: ancilla_var_2 = QuantumVariable(1) qubit_list_2.append(ancilla_var_2[0]) # If qf2 is signed and we augmented Qubits, we need to make sure # the augmented qubits are set to 1 if qf2 is in a negative state # (for details on this check how negative numbers are encoded) if qf2.signed and max_sig > max(significance_range_qf2): for i in range(ancilla_var.size): qs.cx(qf2[-1], ancilla_var[i]) if adder == "cuccaro": cuccaro_procedure( qs, qubit_list_2[:-1], qubit_list_1[:-1], output_qubit=qubit_list_1[-1] ) elif adder == "thapliyal": thapliyal_procedure( qs, qubit_list_2[:-1], qubit_list_1[:-1], output_qubit=qubit_list_1[-1] ) else: raise Exception("Adder " + adder + " not implemented") # Perform 2nd step of the modular addition section # qs.cx(qubit_list_2[-2], qubit_list_1[-1]) qs.cx(qubit_list_2[-1], qubit_list_1[-1]) # Remove the 1s from the augmented qubits and delete if len(augmented_qf2_qbs) != len(qf2.reg): if qf2.signed: for i in range(ancilla_var.size): qs.cx(qf2[-1], ancilla_var[i]) try: ancilla_var.delete() except NameError: pass if qf1.signed and not qf2.signed: ancilla_var_2.delete()
# Workaround for protecting the docstring from the decorator temp = inpl_add.__doc__ inpl_add = gate_wrap(inpl_add) inpl_add.__doc__ = temp # Adder based on https://arxiv.org/abs/1712.02630 def TR_gate(): qc = QuantumCircuit(3) qc.crx(-np.pi / 2, 1, 2) qc.p(-np.pi / 4, 1) qc.cx(0, 1) qc.p(np.pi / 4, 0) qc.crx(np.pi / 2, 0, 2) qc.p(np.pi / 4, 1) qc.crx(np.pi / 2, 1, 2) # Error in Thapliyal paper? Doesnt work if there is no inverse here result = qc.to_gate().inverse() result.name = "TR" return result def thapliyal_procedure(qc, qubit_list_1, qubit_list_2, output_qubit): if len(qubit_list_1) != len(qubit_list_2): raise Exception( "Tried to call Thapliyal-procedure with qubit lists of unequal length" ) n = len(qubit_list_1) # Step 1 for i in range(1, n): qc.cx(qubit_list_1[i], qubit_list_2[i]) # Step 2 qc.cx(qubit_list_1[-1], output_qubit) for i in range(n - 2, 0, -1): qc.cx(qubit_list_1[i], qubit_list_1[i + 1]) # Step 3 for i in range(n - 1): qc.mcx([qubit_list_1[i], qubit_list_2[i]], qubit_list_1[i + 1]) # Step 4 qc.append(TR_gate(), [qubit_list_1[-1], qubit_list_2[-1], output_qubit]) for i in range(n - 2, -1, -1): qc.append(TR_gate(), [qubit_list_1[i], qubit_list_2[i], qubit_list_1[i + 1]]) # Step 5 for i in range(1, n - 1): qc.cx(qubit_list_1[i], qubit_list_1[i + 1]) # Step 6 for i in range(1, n): qc.cx(qubit_list_1[i], qubit_list_2[i])