"""********************************************************************************
* Copyright (c) 2026 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 jax.numpy as jnp
from qrisp.circuit import Qubit
from qrisp.core import QuantumVariable, cx, mcx, x
from qrisp.environments import conjugate, custom_control
from qrisp.jasp import check_for_tracing_mode, jlen, jrange
from qrisp.misc import int_encoder
from qrisp.qtypes import QuantumBool, QuantumFloat
[docs]
@custom_control
def cuccaro_adder(
a: int | QuantumVariable,
b: QuantumVariable,
c_in: QuantumBool | Qubit | None = None,
c_out: QuantumBool | Qubit | None = None,
ctrl: QuantumBool | None = None,
) -> None:
"""In-place adder as introduced in https://arxiv.org/abs/quant-ph/0410184
This function works in both static and dynamic modes. The allowed inputs are both quantum types or one classical
type and one quantum type. Note that when the first input is larger than the second input, the function will perform
modulo addition (relative to the size of the second input) after the first input is truncated to be the same size as
the second input.
The custom control implementation is based on Theorem 2.12 of https://arxiv.org/abs/2407.20167
.. note::
If the first input is quantum and the second classical, the function cannot work as addition is
performed "in-place" on the second input.
Parameters
----------
a : int or QuantumVariable
The value that should be added.
b : QuantumVariable or list[Qubit]
The value that should be modified in the in-place addition.
c_in : QuantumBool or Qubit, optional
An optional carry in value. The default is None.
c_out : QuantumBool or Qubit, optional
An optional carry out value. The default is None.
Raises
------
TypeError
If carry in or carry out is not of type QuantumBool or Qubit in static mode.
ValueError
If the inputs are not valid quantum or classical types.
Returns
-------
None
The function modifies the second input in place.
Examples
--------
Static mode with both quantum inputs:
>>> from qrisp import QuantumFloat, cuccaro_adder
>>> a = QuantumFloat(4)
>>> b = QuantumFloat(4)
>>> a[:] = 4
>>> b[:] = 5
>>> cuccaro_adder(a,b)
>>> print(b)
{9: 1.0}
"""
# convert the classical input to a quantum input
if not isinstance(a, QuantumVariable):
# create a QuantumFloat of the same size as the other quantum input
q_a = b.duplicate()
with conjugate(int_encoder)(q_a, a):
cuccaro_adder(q_a, b, c_in=c_in, c_out=c_out, ctrl=ctrl)
# outside the conjugation, q_a is back in the state |0> and the addition has been performed on b
# delete the temporary quantum variable created for the classical input
q_a.delete()
return
if not isinstance(b, QuantumVariable):
raise ValueError("The second argument must be of type QuantumVariable.")
# when the inputs are of unequal length
# pad the size of the input with the smaller size
dim_a = a.size
dim_b = b.size
max_size = jnp.maximum(dim_a, dim_b)
# reduce the size of a to the size of b if a is larger than b
effective_size_a = jnp.minimum(dim_a, dim_b)
a = a[:effective_size_a]
# create an extension ancilla to change the size of a when it is smaller than b
extension_size = jnp.maximum(0, dim_b - dim_a)
extension_anc_a = QuantumVariable(extension_size)
extended_a = a[:] + extension_anc_a[:]
a = extended_a
dim_a = jlen(a)
dim_b = jlen(b)
ancilla = QuantumFloat(max_size)
if c_in is not None:
if isinstance(c_in, QuantumBool):
c_in = c_in[0]
elif not check_for_tracing_mode() and not isinstance(c_in, Qubit):
raise TypeError(f"c_in must be of type QuantumBool or Qubit, not {type(c_in)}")
cx(c_in, ancilla[0])
if c_out is not None:
if isinstance(c_out, QuantumBool):
ancilla2 = c_out[0]
elif not check_for_tracing_mode() and not isinstance(c_out, Qubit):
raise TypeError(f"c_out must be of type QuantumBool or Qubit, not {type(c_out)}")
else:
ancilla2 = c_out
# first maj gate application
cx(a[0], b[0])
cx(a[0], ancilla[0])
mcx([ancilla[0], b[0]], a[0])
# iterator maj gate application
for i in jrange(1, dim_a):
cx(a[i], b[i])
cx(a[i], a[i - 1])
mcx([a[i - 1], b[i]], a[i])
# cnot
if c_out is not None:
cx(a[-1], ancilla2)
if ctrl is None:
# iterator uma gate application
for j in jrange(dim_a - 1):
# reverse the iteration
i = dim_a - j - 1
x(b[i])
cx(a[i - 1], b[i])
mcx([a[i - 1], b[i]], a[i])
x(b[i])
cx(a[i], a[i - 1])
cx(a[i], b[i])
# last uma gate application
x(b[0])
cx(ancilla[0], b[0])
mcx([ancilla[0], b[0]], a[0])
x(b[0])
cx(a[0], ancilla[0])
cx(a[0], b[0])
else:
# iterator uma gate application
for j in jrange(dim_a - 1):
# reverse the iteration
i = dim_a - j - 1
mcx([a[i - 1], b[i]], a[i])
mcx([ctrl, a[i - 1]], b[i])
cx(a[i], a[i - 1])
cx(a[i], b[i])
# last uma gate application
mcx([ancilla[0], b[0]], a[0])
mcx([ctrl, ancilla[0]], b[0])
cx(a[0], ancilla[0])
cx(a[0], b[0])
if c_in is not None:
cx(c_in, ancilla[0])
# delete the ancilla used for carry bits
ancilla.delete()
# delete the extension ancillas when the inputs are of unequal length
extension_anc_a.delete()