I recently built a dynamic full adder gate with Qiskit. The gate essentially copies the classical computing method for a full adder by emulating the classical gates e.g. AND == Toffoli (Quantum AND gate).

I ran a 5-bit adder with ibm_kyoto (127 qubits Eagle processor), with 10000 shots, expecting the highest result to be the correct one, but it was far from it, with many different combinations of bits being presented as the answer by the algorithm. Please can you help me understand why my algorithm is so bad at finding the addition of two binary numbers, or is Quantum just not suited for this due to NISQ or some fundamental flaw? My code is below:

from qiskit import QuantumCircuit

# a_input and b_input should be binary strings
a_input = "001"  # User input for A
b_input = "101"  # User input for B
n = len(a_input) # Number of bits of numbers added

# Define the full adder function using the provided circuit template
# Initialize the qubits based on user inputs in little-endian order
a_input = a_input[::-1]
b_input = b_input[::-1]
for i in range(n):
# Initialize qubits for "A" inputs (little-endian order)
if a_input[i] == '1':
qc.x(3 * i + 1)  # Map a_input[i] to qubit at position 3*i

# Initialize qubits for "B" inputs (little-endian order)
if b_input[i] == '1':
qc.x(3 * i + 2)  # Map b_input[i] to qubit at position 3*i + 2

# Add the barriers as in the provided template
qc.barrier()

# Apply Toffoli (ccx) and CNOT (cx) gates for the full adder function
qc.ccx(1, 2, 3)
qc.cx(1, 2)
qc.ccx(0, 2, 3)
qc.cx(2, 0)
qc.cx(1, 2)
qc.barrier()

# Repeat for n blocks of qubits as specified by the input n
for offset in range(3, 3 * (n), 3):
# Apply Toffoli (ccx) and CNOT (cx) gates for each block
qc.ccx(1 + offset, 2 + offset, 3 + offset)
qc.cx(1 + offset, 2 + offset)
qc.ccx(0 + offset, 2 + offset, 3 + offset)
qc.cx(2 + offset, 0 + offset)
qc.cx(1 + offset, 2 + offset)
qc.barrier()

# Measure the specified qubits and store results in classical bits
qc.measure([3 * i for i in range(n + 1)], [i for i in range(n + 1)])

# Return the circuit
return qc

# Example usage of the full adder function with user inputs

qc = QuantumCircuit(n*n +1 ,n + 1)

qc.draw(plot_barriers=True, initial_state=True, scale=1, output='mpl')
from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator
from qiskit import transpile

service = QiskitRuntimeService(channel="ibm_quantum", token="*******")
realBackend = service.least_busy(simulator=False, operational=True)

transpiled_qc = transpile(qc, backend=realBackend)

counts = realBackend.run(transpiled_qc, shots=10000).result().get_counts()

print(counts)

import matplotlib.pyplot as plt

def plot_histogram(counts):
# Create a new figure with a wider size
plt.figure(figsize=(15, 6))  # Adjust the width and height as needed

# Plot the histogram
plt.bar(counts.keys(), counts.values())

# Set labels and title
plt.xlabel('Categories')
plt.ylabel('Counts')
plt.title('Histogram')

# Rotate the x-axis labels by 45 degrees
plt.xticks(rotation=45, ha='right')

# Show the plot
plt.show()

plot_histogram(counts)


A 5-bit adder requires several dozen two-qubit gates. Current state of the art two-qubit gate error rates are between 0.1% and 1%. At the time I am writing this post, https://quantum.ibm.com/services/resources?tab=systems&system=ibm_kyoto states the error rate of ibm_kyoto is 3.6%:

If each gate has a 96.4% chance of working right, and you do 50 of them, there's a 16% chance that nothing went wrong. Thus you expect most of the answers to be wrong.

Things would not look so grim with a 0.1% error rate, but even then you will quickly run into walls. This is why quantum error correction is so crucial to running large computations.

Additional operations will indeed be much more relevant to a fault-tolerant computer.

Yet, for a fair comparison, you should run your trials with a fair implementation for each computer.

For example, cold atoms computer basis gates use a CP gate. Therefore, a QFT adder would be better.

Ions/superconductor basis gates are more suitable for ripple-carry adders.

You can run experiments on different computers easily using a compiler like classiq, that chooses the relevant implementation automatically. It also chooses an implementation that suits the width limit of the hardware by automatic auxiliary usage and release

Once you define the high-level model - adder, using the classiq IDE:

qfunc main(output a: qnum, output b: qnum, output res: qnum) {
prepare_int<4>(a);
prepare_int<5>(b);
res = a + b;
}


You can choose a hardware in the synthesis preferences (or your own basis gates):

Synthesize it, let the compiler choose the more fair implementation for that HW, and execute it on the hardware among the list of available backends in the execution page

OYou can also run the experiment from Python, (example in this answer) by defining the execution backend this way:

atoms_hw_setting = CustomHardwareSettings(
basis_gates=["cp", "cz", "u"],
is_symmetric_connectivity=True,
)
preferences = Preferences(custom_hardware_settings=atoms_hw_setting)

qmod_atoms = set_preferences(qmod, preferences)
qprog_atoms = synthesize(qmod_atoms)
show(qprog_atoms)


Or choosing from list of backends as explained in the user guide