# The original version of the following code is sourced from:
# https://github.com/munich-quantum-toolkit/bench/blob/main/src/mqt/bench/benchmarks/hrs_cumulative_multiplier.py
#
# Besides, the current code involves the following file for dependency decoupling:
# https://github.com/Qiskit/qiskit/blob/main/qiskit/synthesis/arithmetic/adders/cdkm_ripple_carry_adder.py#L19
#
# 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):
# - Dependency decoupling: attach `add_ripple_c04` function to this file directly.
# - Refactored to expose unified program interfaces, i.e., QuantumCircuit creation function.
# - Support "kind" argument to select different adder types.
#
# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
# All rights reserved.
#
# SPDX-License-Identifier: MIT
#
# Licensed under the MIT License
#
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2021.
#
# 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.
#
# Modified by MQSC, 2025.
"""HRS Cumulative Multiplier."""
from __future__ import annotations
from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister
# ---------- benchmark registration ----------
from ..benchmark_registry import register_benchmark
from pathlib import Path
@register_benchmark(
Path(__file__).stem,
family=Path(__file__).resolve().parent.name,
description="Compute the product of two equally sized qubit registers using HRS cumulative multiplier",
class_name="MultiplierHRS",
source={
"repo": "https://github.com/munich-quantum-toolkit/bench/tree/main",
"file": "src/mqt/bench/benchmarks/hrs_cumulative_multiplier.py",
"sdk": "Qiskit",
"available_doc": False
},
testability_refactoring=[
"Dependency decoupling",
"Structure reorganization"
]
)
def create_multiplier_hrs(total_qubits: int)-> QuantumCircuit:
return MultiplierHRS(num_qubits=total_qubits)
[docs]
class MultiplierHRS(QuantumCircuit):
"""
Create a Haener-Roetteler-Svore (HRS) cumulative multiplier circuit.
The principle of the HRS cumulative multiplier is based on performing a sequence of controlled
additions of shifted versions of the multiplicand into an accumulator register, conditioned on
the bits of the multiplier.
See Also:
:class:`qiskit.circuit.library.HRSCumulativeMultiplier`
"""
def __init__(self, num_qubits: int, name: str | None = None) -> None:
r"""
Arguments:
num_qubits: The total number of qubits in the circuit, i.e., :math:`n`,
including the two input registers :math:`\ket{a}`, :math:`\ket{b}`
and the output register :math:`\ket{out}`.
Note:
1.
The number of qubits in each input register is determined by
:math:`m = \left\lfloor n/4 \right\rfloor`, and the output register
has :math:`2m` qubits. The remaining qubits are used as carry and
helper qubits for the addition operations.
2.
As a technical debt, we temporarily do not support different kinds
of adders in the HRS multiplier. For future development, the kind
of adder to be used in the multiplier, can be ``"full"``, ``"half"``,
or ``"fixed"``.
"""
self._num_qubits = num_qubits # number of total qubits of the multiplier circuit
self._kind = "half" # kind of adder to be used
super().__init__(name=name or "hrs_cumulative_multiplier")
# Build the circuit
self._build()
[docs]
def adder_ripple_c04(self, num_state_qubits: int, kind: str = "half") -> QuantumCircuit:
r"""A ripple-carry circuit to perform in-place addition on two qubit registers.
This circuit uses :math:`2n + O(1)` CCX gates and :math:`5n + O(1)` CX gates,
at a depth of :math:`2n + O(1)` [1]. The constant depends on the kind
of adder implemented.
As an example, a full ripple-carry adder on 3-qubit registers applies a MAJ
cascade from the least significant bit to the most significant bit, then an
UMA cascade in reverse order to uncompute the carries and write the sum.
Here *MAJ* and *UMA* gates correspond to the gates introduced in [1]. Note that
in this implementation the input register qubits are ordered as all qubits from
the first input register, followed by all qubits from the second input register.
Two different kinds of adders are supported. By setting the ``kind`` argument, you can also
choose a half-adder, which doesn't have a carry-in, and a fixed-sized-adder, which has neither
carry-in nor carry-out, and thus acts on fixed register sizes. Unlike the full-adder,
these circuits need one additional helper qubit.
For the fixed-sized adder (``kind="fixed"``), the same MAJ/UMA structure is
used without a carry-in or carry-out wire.
It has one less qubit than the full-adder since it doesn't have the carry-out, but uses
a helper qubit instead of the carry-in, so it only has one less qubit, not two.
Args:
num_state_qubits: The number of qubits in either input register for
state :math:`|a\rangle` or :math:`|b\rangle`. The two input
registers must have the same number of qubits.
kind: The kind of adder, can be ``"full"`` for a full adder, ``"half"`` for a half
adder, or ``"fixed"`` for a fixed-sized adder. A full adder includes both carry-in
and carry-out, a half only carry-out, and a fixed-sized adder neither carry-in
nor carry-out.
Raises:
ValueError: If ``num_state_qubits`` is lower than 1.
References:
[1] Cuccaro et al., A new quantum ripple-carry addition circuit, 2004.
`arXiv:quant-ph/0410184 <https://arxiv.org/pdf/quant-ph/0410184.pdf>`_
[2] Vedral et al., Quantum Networks for Elementary Arithmetic Operations, 1995.
`arXiv:quant-ph/9511018 <https://arxiv.org/pdf/quant-ph/9511018.pdf>`_
"""
if num_state_qubits < 1:
raise ValueError("The number of qubits must be at least 1.")
circuit = QuantumCircuit()
if kind == "full":
qr_c = QuantumRegister(1, name="cin")
circuit.add_register(qr_c)
else:
qr_c = AncillaRegister(1, name="help")
# Half: [qr_a, qr_b, qr_cout, qr_help]
# Full: [qr_cin, qr_a, qr_b, qr_cout]
# Fixed: [qr_a, qr_b, qr_help]
qr_a = QuantumRegister(num_state_qubits, name="a")
qr_b = QuantumRegister(num_state_qubits, name="b")
circuit.add_register(qr_a, qr_b)
if kind in ["full", "half"]:
qr_z = QuantumRegister(1, name="cout")
circuit.add_register(qr_z)
if kind != "full": # fixed or half
circuit.add_register(qr_c)
# build carry circuit for majority of 3 bits in-place
# corresponds to MAJ gate in [1]
qc_maj = QuantumCircuit(3, name="MAJ")
qc_maj.cx(0, 1)
qc_maj.cx(0, 2)
qc_maj.ccx(2, 1, 0)
maj_gate = qc_maj.to_gate()
# build circuit for reversing carry operation
# corresponds to UMA gate in [1]
qc_uma = QuantumCircuit(3, name="UMA")
qc_uma.ccx(2, 1, 0)
qc_uma.cx(0, 2)
qc_uma.cx(2, 1)
uma_gate = qc_uma.to_gate()
# build ripple-carry adder circuit
circuit.append(maj_gate, [qr_a[0], qr_b[0], qr_c[0]])
for i in range(num_state_qubits - 1):
circuit.append(maj_gate, [qr_a[i + 1], qr_b[i + 1], qr_a[i]])
if kind in ["full", "half"]:
circuit.cx(qr_a[-1], qr_z[0])
for i in reversed(range(num_state_qubits - 1)):
circuit.append(uma_gate, [qr_a[i + 1], qr_b[i + 1], qr_a[i]])
circuit.append(uma_gate, [qr_a[0], qr_b[0], qr_c[0]])
return circuit
[docs]
def create_circuit(self, num_qubits: int, kind: str) -> QuantumCircuit:
"""Create a hrs cumulative multiplier circuit.
Arguments:
num_qubits: Number of qubits of the returned quantum circuit, (num_qubits - 1) must be divisible by 4.
Returns:
QuantumCircuit: The constructed hrs cumulative multiplier circuit.
See Also:
:class:`qiskit.circuit.library.HRSCumulativeMultiplier`
"""
if num_qubits < 5:
msg = "num_qubits must be an integer >= 5."
raise ValueError(msg)
if (num_qubits - 1) % 4 and kind in ['fixed', 'half']:
msg = "(num_qubits - 1) must be divisible by 4 for current kind."
raise ValueError(msg)
if (num_qubits - 2) % 4 and kind == 'full':
msg = "(num_qubits - 2) must be divisible by 4 for current kind."
raise ValueError(msg)
num_state_qubits = num_qubits // 4
num_result_qubits = 2 * num_state_qubits
# define the registers
qr_a = QuantumRegister(num_state_qubits, name="a")
qr_b = QuantumRegister(num_state_qubits, name="b")
qr_out = QuantumRegister(num_result_qubits, name="out")
# prepare adder as controlled gate, where we support three kinds
adder = self.adder_ripple_c04(num_state_qubits, kind=kind)
qr_cin = QuantumRegister(1, name="cin") if kind == "full" else None
# fix: add `cout`
qr_cout = QuantumRegister(1, "cout") if kind in ["half", "full"] else None
# qr_pad = AncillaRegister(1, "pad") if kind == "full" else None
# get the number of helper qubits needed
num_helper_qubits = adder.num_ancillas
# add helper qubits if required
qr_helper = AncillaRegister(num_helper_qubits, "helper") if num_helper_qubits else None
# [qr_a, qr_b, qr_out, qr_cin, qr_cout, qr_pad, qr_helper]
qregs = [qr_a, qr_b, qr_out]
if qr_cin is not None:
qregs.append(qr_cin)
if qr_cout is not None:
qregs.append(qr_cout)
# if qr_pad is not None:
# qregs.append(qr_pad)
if qr_helper is not None:
qregs.append(qr_helper)
# build multiplication circuit
qc = QuantumCircuit(*qregs)
# refine the raw code logic for different kinds
for i in range(num_state_qubits):
num_adder_qubits = num_state_qubits
adder_for_current_step = adder
controlled_adder = adder_for_current_step.to_gate().control(1)
# logic adjustment for different kinds
# currently, we the half mode is supported
# TODO: extend to full and fixed modes
if kind == "half":
qr_list = [qr_a[i], *qr_b[:num_adder_qubits], *qr_out[i : num_state_qubits + i + 1]]
# if kind == "full":
# qr_cin: QuantumRegister
# qr_list = [qr_cin[0], qr_a[i], *qr_b[:num_adder_qubits], *qr_out[i : num_state_qubits + i + 1]]
# elif kind == "half":
# qr_list = [qr_a[i], *qr_b[:num_adder_qubits], *qr_out[i : num_state_qubits + i + 1]]
# elif kind == "fixed":
# qr_list = [qr_a[i], *qr_b[:num_adder_qubits], *qr_out[i : num_state_qubits + i]]
if num_helper_qubits > 0 and qr_helper:
qr_list.extend(qr_helper[:])
qc.append(controlled_adder, qr_list)
# We don't measure the qubits here; measurement can be added as needed.
# qc.measure_all()
qc.name = "hrs_cumulative_multiplier"
return qc
def _build(self) -> None:
"""Build the HRS cumulative multiplier circuit."""
hrs_circuit = self.create_circuit(self._num_qubits, self._kind)
# re-init self with same qregs
super().__init__(*hrs_circuit.qregs)
# compose the multiplier circuit with correct qubit mapping
self.compose(hrs_circuit, inplace=True)