Supervisors:
Dr. Shaukat Ali and Dr. Christoph Laaber
Simula Research Laboratory
Applying differential and metamorphic testing techniques to test IBM Qiskit, Rigetti PyQuil, and Google Cirq
About me
Arfat Salman
Master in Informatics: Programming and System Architecture
Uses quantum physics concepts like superposition and entanglement to achieve computation
IBM Quantum scientist Dr. Maika Takita in lab
Proposed in the 1980s by Richard Feynman and Yuri Manin
A new kind of computing machine
*Not a replacement for classical computer
Due to their unique computational paradigm, quantum computers can solve certain problems exponentially faster and more efficiently than their classical counterparts.
def balanced_fn(n_bit_arg):
if some_condition(n_bit_arg):
return 0
return 1
balanced_fn('001') # => 1
balanced_fn('011') # => 0
balanced_fn('010') # => 0
balanced: Half of inputs return 1, other half 0
constant: 0 or 1 on all inputs
Classical Computer:
Quantum Computer:
We shouldn’t be asking ‘where do quantum speedups come from?’ we should say ‘all computers are quantum, [...]’ and ask ’where do classical slowdowns come from?’
https://quantum.country/
Quantum Bit
Quantum Bit
dead
alive
Quantum Bit
dead
alive
Quantum Bit
dead
alive
Ket
Quantum Bit
Superposition
dead
alive
Quantum Bit
Superposition
dead
alive
Quantum Bit
Superposition
dead
alive
Quantum Bit
Superposition
dead
alive
Quantum Bit
Superposition
probability of observing a 0
probability of observing a 1
Schrödinger's cat
dead
alive
A quantum gate is a unitary matrix that acts on a quantum state and changes it.
Qubit
Flipped Probailities
Hadamard Gate (H):
Single qubit gate that puts a quantum state in superposition
Controlled-NOT gate:
Two-qubit quantum gate
Hadamard Gate (H):
Single qubit gate that puts a quantum state in superposition
Controlled-NOT gate:
Two-qubit quantum gate
Hadamard Gate (H):
Single qubit gate that puts a quantum state in superposition
Controlled-NOT gate:
Two-qubit quantum gate
Quantum computation is a change in the quantum state (i.e., the state of a qubit, the quantum bit).
Quantum Software Platforms
https://thequantuminsider.com/2022/09/05/quantum-computing-companies-ultimate-list-for2022/
Standalone or embedded quantum programming language or API
Quantum simulator that emulates instructions on a classical device
Optimizing compiler that translates high-level language into quantum gate instructions
Software controller that sends analog signals to quantum hardware
pyQuil
Cirq
Microsoft Q#
Google Cirq
Rigetti PyQuil
IBM Qiskit
% of bug-labelled GitHub issues
Microsoft Q#
Google Cirq
Rigetti PyQuil
IBM Qiskit
% of bug-labelled GitHub issues
30
15
24
40
😵
C1: The need for a significant quantity of quantum test programs.
C2: Cross-platform testing is hard due to varying support and APIs, needing manual porting.
C3: Presence of stochasticity.
Lack of generalised automated testing techniques within quantum settings
Quantum Software Testing (and testing at large) is facing two fundamental problems:
Oracle Problem
The Reliable Test-Set problem
Quantum Software Testing (and testing at large) is facing two fundamental problems:
Oracle Problem
The Reliable Test-Set problem
situations where it is extremely difficult, or impossible, to verify the test result of a given test case
the challenge of creating a set of tests that can adequately and accurately assess the functionality and performance of a software system.
This technique involves supplying identical input to comparable applications or distinct implementations of the same application and observing discrepancies in their performance and behaviour.
Solves the Oracle Problem
Python
Source
CPython
PyPy
Same Result
This technique involves supplying identical input to comparable applications or distinct implementations of the same application and observing discrepancies in their performance and behaviour.
Solves the Oracle Problem
Quantum
Algorithm
Qiskit
Cirq
Same Result
after necessary translations
property-based software testing technique
property-based software testing technique
Algorithm
passing
test case
property-based software testing technique
Algorithm
new test case
using algorithm property
passing
test case
property-based software testing technique
Algorithm
new test case
using algorithm property
Same Result
passing
test case
property-based software testing technique
min
min(4,5)
min(5,4)
swapping arguments order does not change answer
Same Result
Algorithm
passing
test case
new test case
using algorithm property
Same Result
property-based software testing technique
min
min(4,5)
min(5,4)
swapping arguments order does not change answer
Same Result
Algorithm
passing
test case
new test case
using algorithm property
Same Result
Source
Follow-up
Each quantum program can be logically divided into six parts:
qr = QuantumRegister(7, name='qr')
cr = ClassicalRegister(7, name='cr')
qc = QuantumCircuit(qr, cr, name='qc')
qc = cirq.Circuit()
qr = [cirq.NamedQubit('q' + str(i)) for i in range(7)]
qc = Program()
cr = qc.declare("ro", "BIT", 7)
Declarations
Cirq
Qiskit
PyQuil
qr = QuantumRegister(7, name='qr')
cr = ClassicalRegister(7, name='cr')
qc = QuantumCircuit(qr, cr, name='qc')
qc = cirq.Circuit()
qr = [cirq.NamedQubit('q' + str(i)) for i in range(7)]
qc = Program()
cr = qc.declare("ro", "BIT", 7)
Declarations (Circuit)
Cirq
Qiskit
PyQuil
qr = QuantumRegister(7, name='qr')
cr = ClassicalRegister(7, name='cr')
qc = QuantumCircuit(qr, cr, name='qc')
qc = cirq.Circuit()
qr = [cirq.NamedQubit('q' + str(i)) for i in range(7)]
qc = Program()
cr = qc.declare("ro", "BIT", 7)
Declarations (Quantum Register)
Cirq
Qiskit
PyQuil
qr = QuantumRegister(7, name='qr')
cr = ClassicalRegister(7, name='cr')
qc = QuantumCircuit(qr, cr, name='qc')
qc = cirq.Circuit()
qr = [cirq.NamedQubit('q' + str(i)) for i in range(7)]
qc = Program()
cr = qc.declare("ro", "BIT", 7)
Declarations (Classical Register)
Cirq
Qiskit
PyQuil
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
qr = QuantumRegister(7, name='qr')
cr = ClassicalRegister(7, name='cr')
qc = QuantumCircuit(qr, cr, name='qc')
import cirq
qc = cirq.Circuit()
qr = [cirq.NamedQubit('q' + str(i)) for i in range(7)]
from pyquil import Program
qc = Program()
cr = qc.declare("ro", "BIT", 7)
Import statements
Cirq
Qiskit
PyQuil
Application of gates
from qiskit.circuit.library.standard_gates import *
qc.append(ZGate(), qargs=[qr[2]], cargs=[])
qc.append(CHGate(), qargs=[qr[2], qr[0]], cargs=[])
qc.append(RYYGate(5.398622178940033), qargs=[qr[0], qr[2]], cargs=[])
import cirq
qc.append(cirq.Z( qr[2] ))
from pyquil.gates import *
qc.inst(Z(2))
Cirq
Qiskit
PyQuil
Application of gates
from qiskit.circuit.library.standard_gates import *
qc.append(ZGate(), qargs=[qr[2]], cargs=[])
qc.append(CHGate(), qargs=[qr[2], qr[0]], cargs=[])
qc.append(RYYGate(5.398622178940033), qargs=[qr[0], qr[2]], cargs=[])
import cirq
qc.append(cirq.Z( qr[2] ))
from pyquil.gates import *
qc.inst(Z(2))
Cirq
Qiskit
PyQuil
Application of gates
from qiskit.circuit.library.standard_gates import *
qc.append(ZGate(), qargs=[qr[2]], cargs=[])
qc.append(CHGate(), qargs=[qr[2], qr[0]], cargs=[])
qc.append(RYYGate(5.398622178940033), qargs=[qr[0], qr[2]], cargs=[])
import cirq
qc.append(cirq.Z( qr[2] ))
qc.append(cirq.H.controlled()( qr[2], qr[0] ))
from pyquil.gates import *
qc.inst(Z(2))
# control qubit 2 and target qubit 0
qc.inst(H(0).controlled(2))
Cirq
Qiskit
PyQuil
Application of gates
from qiskit.circuit.library.standard_gates import *
qc.append(ZGate(), qargs=[qr[2]], cargs=[])
qc.append(CHGate(), qargs=[qr[2], qr[0]], cargs=[])
qc.append(RYYGate(5.398622178940033), qargs=[qr[0], qr[2]], cargs=[])
import cirq
qc.append(cirq.Z( qr[2] ))
qc.append(cirq.H.controlled()( qr[2], qr[0] ))
# ??
from pyquil.gates import *
qc.inst(Z(2))
# control qubit 2 and target qubit 0
qc.inst(H(0).controlled(2))
# ??
Cirq
Qiskit
PyQuil
Application of gates (using bloqs)
from qiskit.circuit.library.standard_gates import *
qc.append(ZGate(), qargs=[qr[2]], cargs=[])
qc.append(CHGate(), qargs=[qr[2], qr[0]], cargs=[])
qc.append(RYYGate(5.398622178940033), qargs=[qr[0], qr[2]], cargs=[])
import cirq
from bloqs.ext.cirq import Gates
qc.append(cirq.Z( qr[2] ))
qc.append(cirq.H.controlled()( qr[2], qr[0] ))
qc.append(Gates.RYYGate(5.398622178940033)( qr[0], qr[2] ))
from pyquil.gates import *
from bloqs.ext.PyQuil import Gates
qc.inst(Z(2))
# control qubit 2 and target qubit 0
qc.inst(H(0).controlled(2))
qc.inst(Gates.RYYGate(5.398622178940033)( 0, 2 ))
Cirq
Qiskit
PyQuil
Application of gates (using bloqs)
from qiskit.circuit.library.standard_gates import *
qc.append(ZGate(), qargs=[qr[2]], cargs=[])
qc.append(CHGate(), qargs=[qr[2], qr[0]], cargs=[])
qc.append(RYYGate(5.398622178940033), qargs=[qr[0], qr[2]], cargs=[])
import cirq
from bloqs.ext.cirq import Gates
qc.append(cirq.Z( qr[2] ))
qc.append(cirq.H.controlled()( qr[2], qr[0] ))
qc.append(Gates.RYYGate(5.398622178940033)( qr[0], qr[2] ))
from pyquil.gates import *
from bloqs.ext.PyQuil import Gates
qc.inst(Z(2))
# control qubit 2 and target qubit 0
qc.inst(H(0).controlled(2))
qc.inst(Gates.RYYGate(5.398622178940033)( 0, 2 ))
Cirq
Qiskit
PyQuil
Application of gates (using bloqs)
from qiskit.circuit.library.standard_gates import *
qc.append(ZGate(), qargs=[qr[2]], cargs=[])
qc.append(CHGate(), qargs=[qr[2], qr[0]], cargs=[])
qc.append(RYYGate(5.398622178940033), qargs=[qr[0], qr[2]], cargs=[])
import cirq
from bloqs.ext.cirq import Gates
qc.append(cirq.Z( qr[2] ))
qc.append(cirq.H.controlled()( qr[2], qr[0] ))
qc.append(Gates.RYYGate(5.398622178940033)( qr[0], qr[2] ))
from pyquil.gates import *
from bloqs.ext.PyQuil import Gates, get_custom_get_definitions
qc = Program()
ryy_defn = get_custom_get_definitions("RYYGate")
qc += ryy_defn
qc.inst(Z(2))
qc.inst(H(0).controlled(2))
qc.inst(Gates.RYYGate(5.398622178940033)( 0, 2 ))
Cirq
Qiskit
PyQuil
Application of gates (measurement gates)
qc.append(ZGate(), qargs=[qr[2]], cargs=[])
qc.append(CHGate(), qargs=[qr[2], qr[0]], cargs=[])
qc.append(RYYGate(5.398622178940033), qargs=[qr[0], qr[2]], cargs=[])
qc.measure(qr, cr)
qc.append(cirq.Z( qr[2] ))
qc.append(cirq.H.controlled()( qr[2], qr[0] ))
qc.append(Gates.RYYGate(5.398622178940033)( qr[0], qr[2] ))
qc.append(cirq.measure(qr[0], key='q0'))
qc.append(cirq.measure(qr[1], key='q1'))
qc.inst(Z(2))
qc.inst(H(0).controlled(2))
qc.inst(Gates.RYYGate(5.398622178940033)( 0, 2 ))
qc += MEASURE(0, qr[0])
qc += MEASURE(1, qr[1])
Cirq
Qiskit
PyQuil
Preparation of the circuit
from qiskit import transpile
qc = transpile(qc, optimization_level=2)
# other options are coupling_map, basis_gates etc.
import cirq
qc = cirq.drop_empty_moments(qc)
qc = cirq.merge_k_qubit_unitaries(qc)
qc = cirq.eject_z(qc)
qc = cirq.drop_negligible_operations(qc)
executable = qvm.compile(qc, protoquil=True, optimize=True)
# some optimization available via quilc and qvm programs
Cirq
Qiskit
PyQuil
Execution of the circuit
from qiskit import Aer, execute
qasm_sim_backend = Aer.get_backend('qasm_simulator')
result = execute(qc, backend=qasm_sim_backend, shots=2048)
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
from pyquil import get_qc
qc.wrap_in_numshots_loop(2048)
qc = get_qc("11q-qvm")
result = qc.run(executable)
Cirq
Qiskit
PyQuil
Execution of the circuit
from qiskit import Aer, execute
qasm_sim_backend = Aer.get_backend('qasm_simulator')
result = execute(qc, backend=qasm_sim_backend, shots=2048)
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
from pyquil import get_qc
qc.wrap_in_numshots_loop(2048)
qc = get_qc("11q-qvm")
result = qc.run(executable)
Cirq
Qiskit
PyQuil
[
AerSimulator("aer_simulator"),
AerSimulator("aer_simulator_statevector"),
AerSimulator("aer_simulator_density_matrix"),
AerSimulator("aer_simulator_stabilizer"),
AerSimulator("aer_simulator_matrix_product_state"),
AerSimulator("aer_simulator_extended_stabilizer"),
AerSimulator("aer_simulator_unitary"),
AerSimulator("aer_simulator_superop"),
QasmSimulator("qasm_simulator"),
StatevectorSimulator("statevector_simulator"),
UnitarySimulator("unitary_simulator"),
PulseSimulator("pulse_simulator"),
]
Execution of the circuit
from qiskit import Aer, execute
qasm_sim_backend = Aer.get_backend('qasm_simulator')
result = execute(qc, backend=qasm_sim_backend, shots=2048)
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
from pyquil import get_qc
qc.wrap_in_numshots_loop(2048)
qc = get_qc("11q-qvm")
result = qc.run(executable)
Cirq
Qiskit
PyQuil
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
Execution of the circuit
from qiskit import Aer, execute
qasm_sim_backend = Aer.get_backend('qasm_simulator')
result = execute(qc, backend=qasm_sim_backend, shots=2048)
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
from pyquil import get_qc
qc.wrap_in_numshots_loop(2048)
qc = get_qc("11q-qvm")
result = qc.run(executable)
Cirq
Qiskit
PyQuil
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
Execution of the circuit
from qiskit import Aer
qasm_sim_backend = Aer.get_backend('qasm_simulator')
result = execute(qc, backend=qasm_sim_backend, shots=2048)
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
from pyquil import get_qc
qc.wrap_in_numshots_loop(2048)
qc = get_qc("11q-qvm")
result = qc.run(executable)
Cirq
Qiskit
PyQuil
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
Collection of results
result = execute(qc, backend=qasm_sim_backend, shots=2048)
counts = result.get_counts(qc)
# {'010': 66, # '000': 198, # '110': 63 ... }
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
result = qc.run(executable)
counts = result.readout_data.get('ro')
# [ [0, 1, 1], [1, 0, 1], [1, 0, 0], [1, 1, 0], ... ]
Cirq
Qiskit
PyQuil
result = simulator.run(circuit, repetitions=2048)
print(result)
# q0=11011110100010101111
# q1=01100101011001110001
Collection of results
result = execute(qc, backend=qasm_sim_backend, shots=2048)
counts = result.get_counts(qc)
# {'010': 66, # '000': 198, # '110': 63 ... }
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
result = qc.run(executable)
counts = result.readout_data.get('ro')
# [ [0, 1, 1], [1, 0, 1], [1, 0, 0], [1, 1, 0], ... ]
Cirq
Qiskit
PyQuil
result = simulator.run(circuit, repetitions=2048)
print(result)
# q0=11011110100010101111
# q1=01100101011001110001
Little- and Big-endian
qr = QuantumRegister(3, name='qr')
cr = ClassicalRegister(3, name='cr')
qc = QuantumCircuit(qr, cr, name='qc')
qc.append(HGate(), qargs=[qr[2]], cargs=[])
qc.measure(qr, cr)
# ... backend selection ...
counts = execute(qc, backend=b, shots=1024)
.result()
.get_counts(qc)
# {'100': 505, '000': 519}
Collection of results (using bloqs)
result = execute(qc, backend=qasm_sim_backend, shots=2048)
counts = result.get_counts(qc)
# {'010': 66, # '000': 198, # '110': 63 ... }
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
from bloqs.ext.PyQuil.utils import get_qiskit_like_output
result = qc.run(executable)
counts = result.readout_data.get('ro')
output = get_qiskit_like_output(data)
# { ... }
Cirq
Qiskit
PyQuil
from bloqs.ext.cirq.utils import get_qiskit_like_output
result = simulator.run(circuit, repetitions=2048)
counts = get_qiskit_like_output(result, keys=['cr0', 'cr1', 'cr2'])
# {'010': 65, # '000': 199, # '110': 66 ... }
Collection of results
result = execute(qc, backend=qasm_sim_backend, shots=2048)
counts = result.get_counts(qc)
# {'010': 66, # '000': 198, # '110': 63 ... }
import cirq
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=2048)
from bloqs.ext.PyQuil.utils import get_qiskit_like_output
result = qc.run(executable)
counts = result.readout_data.get('ro')
output = get_qiskit_like_output(data)
# { ... }
Cirq
Qiskit
PyQuil
from bloqs.ext.cirq.utils import get_qiskit_like_output
result = simulator.run(circuit, repetitions=2048)
counts = get_qiskit_like_output(result, keys=['cr0', 'cr1', 'cr2'])
# {'010': 65, # '000': 199, # '110': 66 ... }
MorphQ Paper
Source Program
as passing test case
MorphQ Paper
Source Program
as passing test case
based on the previous MRs
Follow-up Porgram
MorphQ Paper
Source Program
as passing test case
Follow-up Porgram
based on the previous MRs
Execute
both and
compare
MR | Qiskit | Cirq | PyQuil |
---|---|---|---|
Change Qubit Order | Y | Y | Y |
Inject null-effect operation | Y | Y | Y |
Add quantum register | Y | N | - |
Inject parameters | Y | N | Y |
Partitioned execution | Y | Y | Y |
Intermediary language roundtrip | Y | Y | Y |
Roundtrip conversion via QASM3 | Y | - | - |
Serialization roundtrip | Y | Y | - |
Change of coupling map | Y | Y | N |
Change of gate set | Y | N | N |
Change of optimization level | Y | Y | Y |
Change of backend | Y | Y | Y |
Y = QSP supports given MR
N = No valid API
- = Not a valid MR in the given platform
requires post-processing of the result
maps the qubit indices of source program to new positions and then creates a follow up program by adapting the sequence of gates to the newly mapped qubit indices.
requires post-processing of the result
requires post-processing of the result
requires post-processing of the result
requires post-processing of the result
requires post-processing of the result
requires post-processing of the result
Inserting into the main circuit a sub-circuit that performs a sequence of gate operations followed by its inverse
Inserting into the main circuit a sub-circuit that performs a sequence of gate operations followed by its inverse
qc.rx(math.pi, 2)
qc.x(0)
subcircuit = QuantumCircuit(qr, cr, name='subcircuit')
subcircuit.append(HGate(), qargs=[qr[0]], cargs=[])
qc.append(subcircuit, qargs=qr, cargs=cr)
qc.append(subcircuit.inverse(), qargs=qr, cargs=cr)
qc.h(1) qc.cx(0,1)
Inserting into the main circuit a sub-circuit that performs a sequence of gate operations followed by its inverse
Cirq
Qiskit
PyQuil
qc.append(subcircuit, qargs=qr, cargs=cr)
qc.append(subcircuit.inverse(), qargs=qr, cargs=cr)
subcircuit = Program()
# add gates to sub-circuit
qc.inst(subcircuit)
qc.inst(subcircuit.dagger())
qc = cirq.Circuit() # add gates to qc
subcircuit = cirq.Circuit() # add gates to sub-circuit
qc.append(subcircuit)
qc.append(cirq.inverse(subcircuit))
Enlarging the set of available qubits by adding a new and unused quantum register should not affect the computation on the existing qubits.
# Qiskit
unused_register = QuantumRegister(5, name='extra_registers')
qc.add_register(unused_register)
A comparable API does not exist in Cirq and PyQuil.
Parameterized quantum circuits are quantum circuits that contain one or more parameters (for example, the angle provided to RXGate) that can be adjusted without changing the overall structure of the circuit.
There are some advantages to using parameters:
Qiskit
from qiskit.circuit import Parameter
theta = Parameter('theta')
gamma = Parameter('gamma')
qc.append(RZGate(theta), qargs=[qr[0]], cargs=[])
qc.append(U2Gate(theta, 2.12), qargs=[qr[2]], cargs=[])
qc.append(CRZGate(gamma), qargs=[qr[1], qr[0]], cargs=[])
qc.measure(qr, cr)
# before execution
qc = qc.bind_parameters({
theta: 4.2641612072511235,
gamma: 2.5163050709890156,
})
Qiskit
from qiskit.circuit import Parameter
theta = Parameter('theta')
gamma = Parameter('gamma')
qc.append(RZGate(theta), qargs=[qr[0]], cargs=[])
qc.append(U2Gate(theta, 2.12), qargs=[qr[2]], cargs=[])
qc.append(CRZGate(gamma), qargs=[qr[1], qr[0]], cargs=[])
qc.measure(qr, cr)
# before execution
qc = qc.bind_parameters({
theta: 4.2641612072511235,
gamma: 2.5163050709890156,
})
Qiskit
from qiskit.circuit import Parameter
theta = Parameter('theta')
gamma = Parameter('gamma')
qc.append(RZGate(theta), qargs=[qr[0]], cargs=[])
qc.append(U2Gate(theta, 2.12), qargs=[qr[2]], cargs=[])
qc.append(CRZGate(gamma), qargs=[qr[1], qr[0]], cargs=[])
qc.measure(qr, cr)
# before execution
qc = qc.bind_parameters({
theta: 4.2641612072511235,
gamma: 2.5163050709890156,
})
Qiskit
from qiskit.circuit import Parameter
theta = Parameter('theta')
gamma = Parameter('gamma')
qc.append(RZGate(theta), qargs=[qr[0]], cargs=[])
qc.append(U2Gate(theta, 2.12), qargs=[qr[2]], cargs=[])
qc.append(CRZGate(gamma), qargs=[qr[1], qr[0]], cargs=[])
qc.measure(qr, cr)
# before execution
qc = qc.bind_parameters({
theta: 4.2641612072511235,
gamma: 2.5163050709890156,
})
Cirq
Qiskit
from qiskit.circuit import Parameter
theta = Parameter('theta')
gamma = Parameter('gamma')
qc.append(RZGate(theta), qargs=[qr[0]], cargs=[])
qc.append(U2Gate(theta, 2.12), qargs=[qr[2]], cargs=[])
qc.append(CRZGate(gamma), qargs=[qr[1], qr[0]], cargs=[])
qc.measure(qr, cr)
# before execution
qc = qc.bind_parameters({
theta: 4.2641612072511235,
gamma: 2.5163050709890156,
})
import cirq from sympy import Symbol
theta = Symbol('theta')
gamma = Symbol('gamma')
qc.append(cirq.rz(theta)(qr[0]))
qc.append(Gates.CRZGate(gamma)( qr[1], qr[2] ))
qc = cirq.resolve_parameters(qc, {
"theta": 4.2641612072511235,
"gamma": 2.5163050709890156,
"_lambda": 2.586208953975239,
})
Cirq
Qiskit
PyQuil
from qiskit.circuit import Parameter
theta = Parameter('theta')
gamma = Parameter('gamma')
qc.append(RZGate(theta), qargs=[qr[0]], cargs=[])
qc.append(U2Gate(theta, 2.12), qargs=[qr[2]], cargs=[])
qc.append(CRZGate(gamma), qargs=[qr[1], qr[0]], cargs=[])
qc.measure(qr, cr)
# before execution
qc = qc.bind_parameters({
theta: 4.2641612072511235,
gamma: 2.5163050709890156,
})
theta = qc.declare('theta', 'REAL')
gamma = qc.declare('gamma', 'REAL')
qc.inst(RZ(theta, 0))
qc.inst(Gates.CRZGate(gamma, 1, 2 ))
params = {
"theta": 4.2641612072511235,
"gamma": 2.5163050709890156,
}
for param, value in params.items():
qc.write_memory(region_name=param, value=value)
import cirq from sympy import Symbol
theta = Symbol('theta')
gamma = Symbol('gamma')
qc.append(cirq.rz(theta)(qr[0]))
qc.append(Gates.CRZGate(gamma)( qr[1], qr[2] ))
qc = cirq.resolve_parameters(qc, {
"theta": 4.2641612072511235,
"gamma": 2.5163050709890156,
"_lambda": 2.586208953975239,
})
Source programs might have two subsets of qubits that never interact with each other.
Source programs might have two subsets of qubits that never interact with each other.
Qiskit has OpenQASM2.
Cirq has parital support for OpenQASM2
PyQuil has QUIL
Cirq
Qiskit
PyQuil
qc = qc.from_qasm_str(
qc.qasm()
)
from PyQuil.parser import parse
quil_str = qc.out()
qc = parse(quil_str)
import cirq
from cirq.contrib.qasm_import import circuit_from_qasm
qasm_out = cirq.qasm(qc)
qc = circuit_from_qasm(qasm_out)
QASM 3 format is anticipated to supersede QASM 2 in the near future
Qiskit
from qiskit.qasm3 import loads, dumps
qasm3_out = dumps(qc)
qc = loads(qasm3_out)
Serialization is defined as the process of converting the state of an object into a form that can be persisted or transported.
Cirq
Qiskit
from qiskit.circuit import QuantumCircuit from qiskit import qpy
qc = QuantumCircuit(2, name='Bell')
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
with open('bell.qpy', 'wb') as fd:
qpy.dump(qc, fd)
with open('bell.qpy', 'rb') as fd:
new_qc = qpy.load(fd)[0]
qc = cirq.read_json(
cirq.to_json(qc)
)
q0
q1
q2
q0
q1
q2
q0
q1
q2
Cirq
Qiskit
# Define a custom coupling map
custom_coupling_map = [[0, 1], [1, 2], [2, 3]]
# Transpile the circuit using the custom coupling map
transpiled_qc = transpile(qc, coupling_map=custom_coupling_map)
import networkx as nx
def edge_list_to_cirq_graph(edge_list, nodes=None):
if nodes is None:
num_qubits = len(
set(item for sublist in edge_list for item in sublist)
)
nodes = [
cirq.NamedQubit("q" + str(i)) for i in range(num_qubits)
]
graph = nx.Graph()
for n in nodes:
graph.add_node(n)
for e in edge_list:
graph.add_edge(nodes[e[0]], nodes[e[1]])
return graph
graph = edge_list_to_cirq_graph(qc)
router = cirq.RouteCQC(graph)
routed_qc = router(qc)
This transformation exercise this translation step by replacing the circuit gates in the program with a universal gate set, such as ["rx", "ry", "rz", "p", "cx"]
Qiskit
from qiskit import QuantumCircuit, transpile
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
custom_basis_gates = ['rx', 'ry', 'rz', 'p', 'cx']
transpiled_qc = transpile(qc, basis_gates=custom_basis_gates)
Similar to modifying the optimization level of a traditional compiler, we can change the optimization level of the quantum transpilation process.
Qiskit
from qiskit import QuantumCircuit, transpile
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
transpiled_qc = transpile(qc, optimization_level=1)
Cirq
qc = cirq.eject_phased_paulis(qc)
Different simulators typically have completely different implementations, such as one based on state vectors or density matrices.
Qiskit
Cirq
UNITARY = cirq.unitary(qc)
backend = Aer.get_backend('unitary_simulator')
result = execute(qc.reverse_bits(), backend=backend).result()
UNITARY = result.get_unitary(qc).data
from pyquil.simulation.tools import program_unitary
# 6 is the number of qubits
UNITARY = program_unitary(circuit, 6)
PyQuil
numpy.allcose or cirq.equal_up_to_global_phase
Cirq has limited but growing support for QASM 2 interoperability. Therefore, this rountrip tests Cirq QASM’s generation capabilities, and Qiksit’s foreign QASM ingestion capabilities.
import cirq
from cirq.testing import *
expanded_circuit = cirq.expand_composite(circuit)
qasm_from_qiskit = qiskit.QuantumCircuit.from_qasm_str(
cirq.qasm(expanded_circuit)
).qasm()
cirq_circuit_from_qiskit_qasm = circuit_from_qasm(
qasm_from_qiskit
)
# since, importing in cirq uses different qubit names,
# we need to change the qubit names
circuit_transformed = cirq_circuit_from_qiskit_qasm.transform_qubits(
lambda q: cirq.NamedQubit(q.name.replace("q_", "q"))
)
assert_circuits_with_terminal_measurements_are_equivalent(
circuit_transformed,
circuit
)
Given that each set contains three programs (3 sources and 3 followfups), we carry out pairwise assessments per set, resulting in a total of six comparisons.
We expanded MorphQ’s Qiskit gate-set by incorporating following new available gates: CCZGate, CSGate, CSdgGate, RGate, and RVGate.
All experiments were run on an Apple M1 Pro 14-inch (2021 model) machine. It has ten cores (eight high-performance and two energy efficient), and 16 GB RAM. The OS at the time of evaluation was MacOS Ventura 13.3.1 (22E261)
We did not record any crashes during the translation process.
How many syntactically different but correct programs can be translated by QCross’s converter?
The program converter successfully translates only valid quantum programs, ensures that any possible metamorphic relation is maintained, and QCross is effective in producing numerous warnings and crashes in follow-up programs of different platforms when executed.
QCross identified 14 bugs and 2 potential issues across quantum programming platforms, with Qiskit (8) having the most bugs, followed by PyQuil (4) and Cirq (2).
What has QCross found via cross-platform testing of the widely-used QSSes? i.e., how many warnings and errors do QCross produce?
QCross identified 14 bugs and 2 potential issues across quantum programming platforms, with Qiskit (8) having the most bugs, followed by PyQuil (4) and Cirq (2).
Metamorphic testing proved more effective than differential testing, and ten of the reported bugs have been confirmed as novel by developers.
We regard QCross as an "evolutionary successor" to MorphQ and QDiff. It has uncovered new bugs that were not detected in the previous works, demonstrating its complementary value to the existing research.
How does QCross compare to prior work on testing quantum computing platforms?
The bloqs library was developed to overcome gate set limitations in Cirq and PyQuil, enabling successful translation of Qiskit programs. The library proved essential, as it was required in approximately 94% of translations.
How useful is Bloqs?
Devise a method to better test the divergence of program outputs such that false positives are minimized.
Analyse the existing divergent programs to find out the source of divergence.