qiskit/test/qpy_compat/test_qpy.py

1006 lines
34 KiB
Python
Executable File

#!/usr/bin/env python3
# This code is part of Qiskit.
#
# (C) Copyright IBM 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.
"""Test cases to verify qpy backwards compatibility."""
import argparse
import itertools
import random
import re
import sys
import numpy as np
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.classicalregister import Clbit
from qiskit.circuit.quantumregister import Qubit
from qiskit.circuit.parameter import Parameter
from qiskit.circuit.parametervector import ParameterVector
from qiskit.quantum_info.random import random_unitary
from qiskit.quantum_info import Operator
from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, QFT, DCXGate, PauliGate
from qiskit.circuit.gate import Gate
from qiskit.version import VERSION as current_version_str
try:
from qiskit.qpy import dump, load
except ModuleNotFoundError:
from qiskit.circuit.qpy_serialization import dump, load
# This version pattern is taken from the pypa packaging project:
# https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L223-L254
# which is dual licensed Apache 2.0 and BSD see the source for the original
# authors and other details
VERSION_PATTERN = (
"^"
+ r"""
v?
(?:
(?:(?P<epoch>[0-9]+)!)? # epoch
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
(?P<pre> # pre-release
[-_\.]?
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
[-_\.]?
(?P<pre_n>[0-9]+)?
)?
(?P<post> # post release
(?:-(?P<post_n1>[0-9]+))
|
(?:
[-_\.]?
(?P<post_l>post|rev|r)
[-_\.]?
(?P<post_n2>[0-9]+)?
)
)?
(?P<dev> # dev release
[-_\.]?
(?P<dev_l>dev)
[-_\.]?
(?P<dev_n>[0-9]+)?
)?
)
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
"""
+ "$"
)
def generate_full_circuit():
"""Generate a multiregister circuit with name, metadata, phase."""
qr_a = QuantumRegister(4, "a")
qr_b = QuantumRegister(4, "b")
cr_c = ClassicalRegister(4, "c")
cr_d = ClassicalRegister(4, "d")
full_circuit = QuantumCircuit(
qr_a,
qr_b,
cr_c,
cr_d,
name="MyCircuit",
metadata={"test": 1, "a": 2},
global_phase=3.14159,
)
full_circuit.h(qr_a)
full_circuit.cx(qr_a, qr_b)
full_circuit.barrier(qr_a)
full_circuit.barrier(qr_b)
full_circuit.measure(qr_a, cr_c)
full_circuit.measure(qr_b, cr_d)
return full_circuit
def generate_unitary_gate_circuit():
"""Generate a circuit with a unitary gate."""
unitary_circuit = QuantumCircuit(5, name="unitary_circuit")
unitary_circuit.unitary(random_unitary(32, seed=100), [0, 1, 2, 3, 4])
unitary_circuit.measure_all()
return unitary_circuit
def generate_random_circuits():
"""Generate multiple random circuits."""
random_circuits = []
for i in range(1, 15):
qc = QuantumCircuit(i, name=f"random_circuit-{i}")
qc.h(0)
if i > 1:
for j in range(i - 1):
qc.cx(0, j + 1)
qc.measure_all()
for j in range(i):
qc.reset(j)
qc.x(0).c_if(qc.cregs[0], i)
for j in range(i):
qc.measure(j, j)
random_circuits.append(qc)
return random_circuits
def generate_string_parameters():
"""Generate a circuit for the XYZ pauli string."""
op_circuit = QuantumCircuit(3, name="X^Y^Z")
op_circuit.append(PauliGate("XYZ"), op_circuit.qubits, [])
return op_circuit
def generate_register_edge_cases():
"""Generate register edge case circuits."""
register_edge_cases = []
# Circuit with shared bits in a register
qubits = [Qubit() for _ in range(5)]
shared_qc = QuantumCircuit(name="shared_bits")
shared_qc.add_bits(qubits)
shared_qr = QuantumRegister(bits=qubits)
shared_qc.add_register(shared_qr)
shared_qc.h(shared_qr)
shared_qc.cx(0, 1)
shared_qc.cx(0, 2)
shared_qc.cx(0, 3)
shared_qc.cx(0, 4)
shared_qc.measure_all()
register_edge_cases.append(shared_qc)
# Circuit with registers that have a mix of standalone and shared register
# bits
qr = QuantumRegister(5, "foo")
qr = QuantumRegister(name="bar", bits=qr[:3] + [Qubit(), Qubit()])
cr = ClassicalRegister(5, "foo")
cr = ClassicalRegister(name="classical_bar", bits=cr[:3] + [Clbit(), Clbit()])
hybrid_qc = QuantumCircuit(qr, cr, name="mix_standalone_bits_registers")
hybrid_qc.h(0)
hybrid_qc.cx(0, 1)
hybrid_qc.cx(0, 2)
hybrid_qc.cx(0, 3)
hybrid_qc.cx(0, 4)
hybrid_qc.measure(qr, cr)
register_edge_cases.append(hybrid_qc)
# Circuit with mixed standalone and shared registers
qubits = [Qubit() for _ in range(5)]
clbits = [Clbit() for _ in range(5)]
mixed_qc = QuantumCircuit(name="mix_standalone_bits_with_registers")
mixed_qc.add_bits(qubits)
mixed_qc.add_bits(clbits)
qr = QuantumRegister(bits=qubits)
cr = ClassicalRegister(bits=clbits)
mixed_qc.add_register(qr)
mixed_qc.add_register(cr)
qr_standalone = QuantumRegister(2, "standalone")
mixed_qc.add_register(qr_standalone)
cr_standalone = ClassicalRegister(2, "classical_standalone")
mixed_qc.add_register(cr_standalone)
mixed_qc.unitary(random_unitary(32, seed=42), qr)
mixed_qc.unitary(random_unitary(4, seed=100), qr_standalone)
mixed_qc.measure(qr, cr)
mixed_qc.measure(qr_standalone, cr_standalone)
register_edge_cases.append(mixed_qc)
# Circuit with out of order register bits
qr_standalone = QuantumRegister(2, "standalone")
qubits = [Qubit() for _ in range(5)]
clbits = [Clbit() for _ in range(5)]
ooo_qc = QuantumCircuit(name="out_of_order_bits")
ooo_qc.add_bits(qubits)
ooo_qc.add_bits(clbits)
random.seed(42)
random.shuffle(qubits)
random.shuffle(clbits)
qr = QuantumRegister(bits=qubits)
cr = ClassicalRegister(bits=clbits)
ooo_qc.add_register(qr)
ooo_qc.add_register(cr)
qr_standalone = QuantumRegister(2, "standalone")
cr_standalone = ClassicalRegister(2, "classical_standalone")
ooo_qc.add_bits([qr_standalone[1], qr_standalone[0]])
ooo_qc.add_bits([cr_standalone[1], cr_standalone[0]])
ooo_qc.add_register(qr_standalone)
ooo_qc.add_register(cr_standalone)
ooo_qc.unitary(random_unitary(32, seed=42), qr)
ooo_qc.unitary(random_unitary(4, seed=100), qr_standalone)
ooo_qc.measure(qr, cr)
ooo_qc.measure(qr_standalone, cr_standalone)
register_edge_cases.append(ooo_qc)
return register_edge_cases
def generate_parameterized_circuit():
"""Generate a circuit with parameters and parameter expressions."""
param_circuit = QuantumCircuit(1, name="parameterized")
theta = Parameter("theta")
lam = Parameter("λ")
theta_pi = 3.14159 * theta
pe = theta_pi / lam
param_circuit.append(U3Gate(theta, theta_pi, lam), [0])
param_circuit.append(U1Gate(pe), [0])
param_circuit.append(U2Gate(theta_pi, lam), [0])
return param_circuit
def generate_qft_circuit():
"""Generate a QFT circuit with initialization."""
k = 5
state = (1 / np.sqrt(8)) * np.array(
[
np.exp(-1j * 2 * np.pi * k * (0) / 8),
np.exp(-1j * 2 * np.pi * k * (1) / 8),
np.exp(-1j * 2 * np.pi * k * (2) / 8),
np.exp(-1j * 2 * np.pi * k * 3 / 8),
np.exp(-1j * 2 * np.pi * k * 4 / 8),
np.exp(-1j * 2 * np.pi * k * 5 / 8),
np.exp(-1j * 2 * np.pi * k * 6 / 8),
np.exp(-1j * 2 * np.pi * k * 7 / 8),
]
)
qubits = 3
qft_circ = QuantumCircuit(qubits, qubits, name="QFT")
qft_circ.initialize(state)
qft_circ.append(QFT(qubits), range(qubits))
qft_circ.measure(range(qubits), range(qubits))
return qft_circ
def generate_param_phase():
"""Generate circuits with parameterize global phase."""
output_circuits = []
# Generate circuit with ParameterExpression global phase
theta = Parameter("theta")
phi = Parameter("phi")
sum_param = theta + phi
qc = QuantumCircuit(5, 1, global_phase=sum_param, name="parameter_phase")
qc.h(0)
for i in range(4):
qc.cx(i, i + 1)
qc.barrier()
qc.rz(sum_param, range(3))
qc.rz(phi, 3)
qc.rz(theta, 4)
qc.barrier()
for i in reversed(range(4)):
qc.cx(i, i + 1)
qc.h(0)
qc.measure(0, 0)
output_circuits.append(qc)
# Generate circuit with Parameter global phase
theta = Parameter("theta")
bell_qc = QuantumCircuit(2, global_phase=theta, name="bell_param_global_phase")
bell_qc.h(0)
bell_qc.cx(0, 1)
bell_qc.measure_all()
output_circuits.append(bell_qc)
return output_circuits
def generate_single_clbit_condition_teleportation(): # pylint: disable=invalid-name
"""Generate single clbit condition teleportation circuit."""
qr = QuantumRegister(1)
cr = ClassicalRegister(2, name="name")
teleport_qc = QuantumCircuit(qr, cr, name="Reset Test")
teleport_qc.x(0)
teleport_qc.measure(0, cr[0])
teleport_qc.x(0).c_if(cr[0], 1)
teleport_qc.measure(0, cr[1])
return teleport_qc
def generate_parameter_vector():
"""Generate tests for parameter vector element ordering."""
qc = QuantumCircuit(11, name="parameter_vector")
input_params = ParameterVector("x_par", 11)
user_params = ParameterVector("θ_par", 11)
for i, param in enumerate(user_params):
qc.ry(param, i)
for i, param in enumerate(input_params):
qc.rz(param, i)
return qc
def generate_parameter_vector_expression(): # pylint: disable=invalid-name
"""Generate tests for parameter vector element ordering."""
qc = QuantumCircuit(7, name="vector_expansion")
entanglement = [[i, i + 1] for i in range(7 - 1)]
input_params = ParameterVector("x_par", 14)
user_params = ParameterVector("\u03B8_par", 1)
for i in range(qc.num_qubits):
qc.ry(user_params[0], qc.qubits[i])
for source, target in entanglement:
qc.cz(qc.qubits[source], qc.qubits[target])
for i in range(qc.num_qubits):
qc.rz(-2 * input_params[2 * i + 1], qc.qubits[i])
qc.rx(-2 * input_params[2 * i], qc.qubits[i])
return qc
def generate_evolution_gate():
"""Generate a circuit with a pauli evolution gate."""
# Runtime import since this only exists in terra 0.19.0
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.synthesis import SuzukiTrotter
from qiskit.quantum_info import SparsePauliOp
synthesis = SuzukiTrotter()
op = SparsePauliOp.from_list([("ZI", 1), ("IZ", 1)])
evo = PauliEvolutionGate([op] * 5, time=2.0, synthesis=synthesis)
qc = QuantumCircuit(2, name="pauli_evolution_circuit")
qc.append(evo, range(2))
return qc
def generate_control_flow_circuits():
"""Test qpy serialization with control flow instructions."""
from qiskit.circuit.controlflow import WhileLoopOp, IfElseOp, ForLoopOp
# If instruction
circuits = []
qc = QuantumCircuit(2, 2, name="control_flow")
qc.h(0)
qc.measure(0, 0)
true_body = QuantumCircuit(1)
true_body.x(0)
if_op = IfElseOp((qc.clbits[0], True), true_body=true_body)
qc.append(if_op, [1])
qc.measure(1, 1)
circuits.append(qc)
# If else instruction
qc = QuantumCircuit(2, 2, name="if_else")
qc.h(0)
qc.measure(0, 0)
false_body = QuantumCircuit(1)
false_body.y(0)
if_else_op = IfElseOp((qc.clbits[0], True), true_body, false_body)
qc.append(if_else_op, [1])
qc.measure(1, 1)
circuits.append(qc)
# While loop
qc = QuantumCircuit(2, 1, name="while_loop")
block = QuantumCircuit(2, 1)
block.h(0)
block.cx(0, 1)
block.measure(0, 0)
while_loop = WhileLoopOp((qc.clbits[0], 0), block)
qc.append(while_loop, [0, 1], [0])
circuits.append(qc)
# for loop range
qc = QuantumCircuit(2, 1, name="for_loop")
body = QuantumCircuit(2, 1)
body.h(0)
body.cx(0, 1)
body.measure(0, 0)
body.break_loop().c_if(0, True)
for_loop_op = ForLoopOp(range(5), None, body=body)
qc.append(for_loop_op, [0, 1], [0])
circuits.append(qc)
# For loop iterator
qc = QuantumCircuit(2, 1, name="for_loop_iterator")
for_loop_op = ForLoopOp(iter(range(5)), None, body=body)
qc.append(for_loop_op, [0, 1], [0])
circuits.append(qc)
return circuits
def generate_control_flow_switch_circuits():
"""Generate circuits with switch-statement instructions."""
from qiskit.circuit.controlflow import CASE_DEFAULT
circuits = []
qc = QuantumCircuit(2, 1, name="switch_clbit")
case_t = qc.copy_empty_like()
case_t.x(0)
case_f = qc.copy_empty_like()
case_f.z(1)
qc.switch(qc.clbits[0], [(True, case_t), (False, case_f)], qc.qubits, qc.clbits)
circuits.append(qc)
qreg = QuantumRegister(2, "q")
creg = ClassicalRegister(3, "c")
qc = QuantumCircuit(qreg, creg, name="switch_creg")
case_0 = QuantumCircuit(qreg, creg)
case_0.x(0)
case_1 = QuantumCircuit(qreg, creg)
case_1.z(1)
case_2 = QuantumCircuit(qreg, creg)
case_2.x(1)
qc.switch(
creg, [(0, case_0), ((1, 2), case_1), ((3, 4, CASE_DEFAULT), case_2)], qc.qubits, qc.clbits
)
circuits.append(qc)
return circuits
def generate_schedule_blocks():
"""Standard QPY testcase for schedule blocks."""
from qiskit.pulse import builder, channels, library
current_version = current_version_str.split(".")
for i in range(len(current_version[2])):
if current_version[2][i].isalpha():
current_version[2] = current_version[2][:i]
break
current_version = tuple(int(x) for x in current_version)
# Parameterized schedule test is avoided.
# Generated reference and loaded QPY object may induce parameter uuid mismatch.
# As workaround, we need test with bounded parameters, however, schedule.parameters
# are returned as Set and thus its order is random.
# Since schedule parameters are validated, we cannot assign random numbers.
# We need to upgrade testing framework.
schedule_blocks = []
# Instructions without parameters
with builder.build() as block:
with builder.align_sequential():
builder.set_frequency(5e9, channels.DriveChannel(0))
builder.shift_frequency(10e6, channels.DriveChannel(1))
builder.set_phase(1.57, channels.DriveChannel(0))
builder.shift_phase(0.1, channels.DriveChannel(1))
builder.barrier(channels.DriveChannel(0), channels.DriveChannel(1))
gaussian_amp = 0.1
gaussian_angle = 0.7
if current_version < (1, 0, 0):
builder.play(
library.Gaussian(160, gaussian_amp * np.exp(1j * gaussian_angle), 40),
channels.DriveChannel(0),
)
else:
builder.play(
library.Gaussian(160, gaussian_amp, 40, gaussian_angle),
channels.DriveChannel(0),
)
builder.play(library.GaussianSquare(800, 0.1, 64, 544), channels.ControlChannel(0))
builder.play(library.Drag(160, 0.1, 40, 1.5), channels.DriveChannel(1))
builder.play(library.Constant(800, 0.1), channels.MeasureChannel(0))
builder.acquire(1000, channels.AcquireChannel(0), channels.MemorySlot(0))
schedule_blocks.append(block)
# Raw symbolic pulse
import symengine as sym
duration, amp, t = sym.symbols("duration amp t") # pylint: disable=invalid-name
expr = amp * sym.sin(2 * sym.pi * t / duration)
my_pulse = library.SymbolicPulse(
pulse_type="Sinusoidal",
duration=100,
parameters={"amp": 0.1},
envelope=expr,
valid_amp_conditions=sym.Abs(amp) <= 1.0,
)
with builder.build() as block:
builder.play(my_pulse, channels.DriveChannel(0))
schedule_blocks.append(block)
# Raw waveform
my_waveform = 0.1 * np.sin(2 * np.pi * np.linspace(0, 1, 100))
with builder.build() as block:
builder.play(my_waveform, channels.DriveChannel(0))
schedule_blocks.append(block)
return schedule_blocks
def generate_referenced_schedule():
"""Test for QPY serialization of unassigned reference schedules."""
from qiskit.pulse import builder, channels, library
schedule_blocks = []
# Completely unassigned schedule
with builder.build() as block:
builder.reference("cr45p", "q0", "q1")
builder.reference("x", "q0")
builder.reference("cr45m", "q0", "q1")
schedule_blocks.append(block)
# Partly assigned schedule
with builder.build() as x_q0:
builder.play(library.Constant(100, 0.1), channels.DriveChannel(0))
with builder.build() as block:
builder.reference("cr45p", "q0", "q1")
builder.call(x_q0)
builder.reference("cr45m", "q0", "q1")
schedule_blocks.append(block)
return schedule_blocks
def generate_calibrated_circuits():
"""Test for QPY serialization with calibrations."""
from qiskit.pulse import builder, Constant, DriveChannel
circuits = []
# custom gate
mygate = Gate("mygate", 1, [])
qc = QuantumCircuit(1, name="calibrated_circuit_1")
qc.append(mygate, [0])
with builder.build() as caldef:
builder.play(Constant(100, 0.1), DriveChannel(0))
qc.add_calibration(mygate, (0,), caldef)
circuits.append(qc)
# override instruction
qc = QuantumCircuit(1, name="calibrated_circuit_2")
qc.x(0)
with builder.build() as caldef:
builder.play(Constant(100, 0.1), DriveChannel(0))
qc.add_calibration("x", (0,), caldef)
circuits.append(qc)
return circuits
def generate_controlled_gates():
"""Test QPY serialization with custom ControlledGates."""
circuits = []
qc = QuantumCircuit(3, name="custom_controlled_gates")
controlled_gate = DCXGate().control(1)
qc.append(controlled_gate, [0, 1, 2])
circuits.append(qc)
custom_gate = Gate("black_box", 1, [])
custom_definition = QuantumCircuit(1)
custom_definition.h(0)
custom_definition.sdg(0)
custom_gate.definition = custom_definition
nested_qc = QuantumCircuit(3, name="nested_qc")
qc.append(custom_gate, [0])
controlled_gate = custom_gate.control(2)
nested_qc.append(controlled_gate, [0, 1, 2])
circuits.append(nested_qc)
qc_open = QuantumCircuit(2, name="open_cx")
qc_open.cx(0, 1, ctrl_state=0)
circuits.append(qc_open)
return circuits
def generate_open_controlled_gates():
"""Test QPY serialization with custom ControlledGates with open controls."""
circuits = []
qc = QuantumCircuit(3, name="open_controls_simple")
controlled_gate = DCXGate().control(1, ctrl_state=0)
qc.append(controlled_gate, [0, 1, 2])
circuits.append(qc)
custom_gate = Gate("black_box", 1, [])
custom_definition = QuantumCircuit(1)
custom_definition.h(0)
custom_definition.sdg(0)
custom_gate.definition = custom_definition
nested_qc = QuantumCircuit(3, name="open_controls_nested")
nested_qc.append(custom_gate, [0])
controlled_gate = custom_gate.control(2, ctrl_state=1)
nested_qc.append(controlled_gate, [0, 1, 2])
circuits.append(nested_qc)
return circuits
def generate_acquire_instruction_with_kernel_and_discriminator():
"""Test QPY serialization with Acquire instruction with kernel and discriminator."""
from qiskit.pulse import builder, AcquireChannel, MemorySlot, Discriminator, Kernel
schedule_blocks = []
with builder.build() as block:
builder.acquire(
100,
AcquireChannel(0),
MemorySlot(0),
kernel=Kernel(
name="my_kernel", my_params_1={"param1": 0.1, "param2": 0.2}, my_params_2=[0, 1]
),
)
schedule_blocks.append(block)
with builder.build() as block:
builder.acquire(
100,
AcquireChannel(0),
MemorySlot(0),
discriminator=Discriminator(
name="my_disc", my_params_1={"param1": 0.1, "param2": 0.2}, my_params_2=[0, 1]
),
)
schedule_blocks.append(block)
return schedule_blocks
def generate_layout_circuits():
"""Test qpy circuits with layout set."""
from qiskit.transpiler.layout import TranspileLayout, Layout
qr = QuantumRegister(3, "foo")
qc = QuantumCircuit(qr, name="GHZ with layout")
qc.h(0)
qc.cx(0, 1)
qc.swap(0, 1)
qc.cx(0, 2)
input_layout = {qr[index]: index for index in range(len(qc.qubits))}
qc._layout = TranspileLayout(
Layout(input_layout),
input_qubit_mapping=input_layout,
final_layout=Layout.from_qubit_list([qc.qubits[1], qc.qubits[0], qc.qubits[2]]),
)
return [qc]
def generate_clifford_circuits():
"""Test qpy circuits with Clifford operations."""
from qiskit.quantum_info import Clifford
cliff = Clifford.from_dict(
{
"stabilizer": ["-IZX", "+ZYZ", "+ZII"],
"destabilizer": ["+ZIZ", "+ZXZ", "-XIX"],
}
)
qc = QuantumCircuit(3, name="Clifford Circuits")
qc.append(cliff, [0, 1, 2])
return [qc]
def generate_annotated_circuits():
"""Test qpy circuits with annotated operations."""
from qiskit.circuit import AnnotatedOperation, ControlModifier, InverseModifier, PowerModifier
from qiskit.circuit.library import XGate, CXGate
op1 = AnnotatedOperation(
CXGate(), [InverseModifier(), ControlModifier(1), PowerModifier(1.4), InverseModifier()]
)
op2 = AnnotatedOperation(XGate(), InverseModifier())
qc = QuantumCircuit(6, 1, name="Annotated circuits")
qc.cx(0, 1)
qc.append(op1, [0, 1, 2])
qc.h(4)
qc.append(op2, [1])
return [qc]
def generate_control_flow_expr():
"""`IfElseOp`, `WhileLoopOp` and `SwitchCaseOp` with `Expr` nodes in their discriminators."""
from qiskit.circuit.classical import expr, types
body1 = QuantumCircuit(1)
body1.x(0)
qr1 = QuantumRegister(2, "q1")
cr1 = ClassicalRegister(2, "c1")
qc1 = QuantumCircuit(qr1, cr1, name="cf-expr1")
qc1.if_test(expr.equal(cr1, 3), body1.copy(), [0], [])
qc1.while_loop(expr.logic_not(cr1[1]), body1.copy(), [0], [])
inner2 = QuantumCircuit(1)
inner2.x(0)
outer2 = QuantumCircuit(1, 1)
outer2.if_test(expr.logic_not(outer2.clbits[0]), inner2, [0], [])
qr2 = QuantumRegister(2, "q2")
cr1_2 = ClassicalRegister(3, "c1")
cr2_2 = ClassicalRegister(3, "c2")
qc2 = QuantumCircuit(qr2, cr1_2, cr2_2, name="cf-expr2")
qc2.if_test(expr.logic_or(expr.less(cr1_2, cr2_2), cr1_2[1]), outer2, [1], [1])
inner3 = QuantumCircuit(1)
inner3.x(0)
outer3 = QuantumCircuit(1, 1)
outer3.switch(expr.logic_not(outer2.clbits[0]), [(False, inner2)], [0], [])
qr3 = QuantumRegister(2, "q2")
cr1_3 = ClassicalRegister(3, "c1")
cr2_3 = ClassicalRegister(3, "c2")
qc3 = QuantumCircuit(qr3, cr1_3, cr2_3, name="cf-expr3")
qc3.switch(expr.bit_xor(cr1_3, cr2_3), [(0, outer2)], [1], [1])
cr1_4 = ClassicalRegister(256, "c1")
cr2_4 = ClassicalRegister(4, "c2")
cr3_4 = ClassicalRegister(4, "c3")
inner4 = QuantumCircuit(1)
inner4.x(0)
outer_loose = Clbit()
outer4 = QuantumCircuit(QuantumRegister(2, "q_outer"), cr2_4, [outer_loose], cr1_4)
outer4.if_test(
expr.logic_and(
expr.logic_or(
expr.greater(expr.bit_or(cr2_4, 7), 10),
expr.equal(expr.bit_and(cr1_4, cr1_4), expr.bit_not(cr1_4)),
),
expr.logic_or(
outer_loose,
expr.cast(cr1_4, types.Bool()),
),
),
inner4,
[0],
[],
)
qc4_loose = Clbit()
qc4 = QuantumCircuit(
QuantumRegister(2, "qr4"), cr1_4, cr2_4, cr3_4, [qc4_loose], name="cf-expr4"
)
qc4.rz(np.pi, 0)
qc4.switch(
expr.logic_and(
expr.logic_or(
expr.logic_or(
expr.less(cr2_4, cr3_4),
expr.logic_not(expr.greater_equal(cr3_4, cr2_4)),
),
expr.logic_or(
expr.logic_not(expr.less_equal(cr3_4, cr2_4)),
expr.greater(cr2_4, cr3_4),
),
),
expr.logic_and(
expr.equal(cr3_4, 2),
expr.not_equal(expr.bit_xor(cr1_4, 0x0F), 0x0F),
),
),
[(False, outer4)],
[1, 0],
list(cr2_4) + [qc4_loose] + list(cr1_4),
)
qc4.rz(np.pi, 0)
return [qc1, qc2, qc3, qc4]
def generate_standalone_var():
"""Circuits that use standalone variables."""
import uuid
from qiskit.circuit.classical import expr, types
# This is the low-level, non-preferred way to construct variables, but we need the UUIDs to be
# deterministic between separate invocations of the script.
uuids = [
uuid.UUID(bytes=b"hello, qpy world", version=4),
uuid.UUID(bytes=b"not a good uuid4", version=4),
uuid.UUID(bytes=b"but it's ok here", version=4),
uuid.UUID(bytes=b"any old 16 bytes", version=4),
uuid.UUID(bytes=b"and another load", version=4),
]
a = expr.Var(uuids[0], types.Bool(), name="a")
b = expr.Var(uuids[1], types.Bool(), name="θψφ")
b_other = expr.Var(uuids[2], types.Bool(), name=b.name)
c = expr.Var(uuids[3], types.Uint(8), name="🐍🐍🐍")
d = expr.Var(uuids[4], types.Uint(8), name="d")
qc = QuantumCircuit(1, 1, inputs=[a], name="standalone_var")
qc.add_var(b, expr.logic_not(a))
qc.add_var(c, expr.lift(0, c.type))
with qc.if_test(b) as else_:
qc.store(c, expr.lift(3, c.type))
with qc.while_loop(b):
qc.add_var(c, expr.lift(7, c.type))
with else_:
qc.add_var(d, expr.lift(7, d.type))
qc.measure(0, 0)
with qc.switch(c) as case:
with case(0):
qc.store(b, True)
with case(1):
qc.store(qc.clbits[0], False)
with case(2):
# Explicit shadowing.
qc.add_var(b_other, True)
with case(3):
qc.store(a, False)
with case(case.DEFAULT):
pass
return [qc]
def generate_v12_expr():
"""Circuits that contain the `Index` and bitshift operators new in QPY v12."""
import uuid
from qiskit.circuit.classical import expr, types
a = expr.Var(uuid.UUID(bytes=b"hello, qpy world", version=4), types.Uint(8), name="a")
cr = ClassicalRegister(4, "cr")
index = QuantumCircuit(cr, inputs=[a], name="index_expr")
index.store(expr.index(cr, 0), expr.index(a, a))
shift = QuantumCircuit(cr, inputs=[a], name="shift_expr")
with shift.if_test(expr.equal(expr.shift_right(expr.shift_left(a, 1), 1), a)):
pass
return [index, shift]
def generate_circuits(version_parts):
"""Generate reference circuits."""
output_circuits = {
"full.qpy": [generate_full_circuit()],
"unitary.qpy": [generate_unitary_gate_circuit()],
"multiple.qpy": generate_random_circuits(),
"string_parameters.qpy": [generate_string_parameters()],
"register_edge_cases.qpy": generate_register_edge_cases(),
"parameterized.qpy": [generate_parameterized_circuit()],
}
if version_parts is None:
return output_circuits
if version_parts >= (0, 18, 1):
output_circuits["qft_circuit.qpy"] = [generate_qft_circuit()]
output_circuits["teleport.qpy"] = [generate_single_clbit_condition_teleportation()]
if version_parts >= (0, 19, 0):
output_circuits["param_phase.qpy"] = generate_param_phase()
if version_parts >= (0, 19, 1):
output_circuits["parameter_vector.qpy"] = [generate_parameter_vector()]
output_circuits["pauli_evo.qpy"] = [generate_evolution_gate()]
output_circuits["parameter_vector_expression.qpy"] = [
generate_parameter_vector_expression()
]
if version_parts >= (0, 19, 2):
output_circuits["control_flow.qpy"] = generate_control_flow_circuits()
if version_parts >= (0, 21, 0):
output_circuits["schedule_blocks.qpy"] = generate_schedule_blocks()
output_circuits["pulse_gates.qpy"] = generate_calibrated_circuits()
if version_parts >= (0, 24, 0):
output_circuits["referenced_schedule_blocks.qpy"] = generate_referenced_schedule()
output_circuits["control_flow_switch.qpy"] = generate_control_flow_switch_circuits()
if version_parts >= (0, 24, 1):
output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates()
output_circuits["controlled_gates.qpy"] = generate_controlled_gates()
if version_parts >= (0, 24, 2):
output_circuits["layout.qpy"] = generate_layout_circuits()
if version_parts >= (0, 25, 0):
output_circuits["acquire_inst_with_kernel_and_disc.qpy"] = (
generate_acquire_instruction_with_kernel_and_discriminator()
)
output_circuits["control_flow_expr.qpy"] = generate_control_flow_expr()
if version_parts >= (0, 45, 2):
output_circuits["clifford.qpy"] = generate_clifford_circuits()
if version_parts >= (1, 0, 0):
output_circuits["annotated.qpy"] = generate_annotated_circuits()
if version_parts >= (1, 1, 0):
output_circuits["standalone_vars.qpy"] = generate_standalone_var()
output_circuits["v12_expr.qpy"] = generate_v12_expr()
return output_circuits
def assert_equal(reference, qpy, count, version_parts, bind=None, equivalent=False):
"""Compare two circuits."""
if bind is not None:
reference_parameter_names = [x.name for x in reference.parameters]
qpy_parameter_names = [x.name for x in qpy.parameters]
if reference_parameter_names != qpy_parameter_names:
msg = (
f"Circuit {count} parameter mismatch:"
f" {reference_parameter_names} != {qpy_parameter_names}"
)
sys.stderr.write(msg)
sys.exit(4)
reference = reference.assign_parameters(bind)
qpy = qpy.assign_parameters(bind)
if equivalent:
if not Operator.from_circuit(reference).equiv(Operator.from_circuit(qpy)):
msg = (
f"Reference Circuit {count}:\n{reference}\nis not equivalent to "
f"qpy loaded circuit {count}:\n{qpy}\n"
)
sys.stderr.write(msg)
sys.exit(1)
else:
if reference != qpy:
msg = (
f"Reference Circuit {count}:\n{reference}\nis not equivalent to "
f"qpy loaded circuit {count}:\n{qpy}\n"
)
sys.stderr.write(msg)
sys.exit(1)
# Check deprecated bit properties, if set. The QPY dumping code before Terra 0.23.2 didn't
# include enough information for us to fully reconstruct this, so we only test if newer.
if version_parts >= (0, 23, 2) and isinstance(reference, QuantumCircuit):
for ref_bit, qpy_bit in itertools.chain(
zip(reference.qubits, qpy.qubits), zip(reference.clbits, qpy.clbits)
):
if ref_bit._register is not None and ref_bit != qpy_bit:
msg = (
f"Reference Circuit {count}:\n"
"deprecated bit-level register information mismatch\n"
f"reference bit: {ref_bit}\n"
f"loaded bit: {qpy_bit}\n"
)
sys.stderr.write(msg)
sys.exit(1)
if (
version_parts >= (0, 24, 2)
and isinstance(reference, QuantumCircuit)
and reference.layout != qpy.layout
):
msg = f"Circuit {count} layout mismatch {reference.layout} != {qpy.layout}\n"
sys.stderr.write(msg)
sys.exit(4)
# Don't compare name on bound circuits
if bind is None and reference.name != qpy.name:
msg = f"Circuit {count} name mismatch {reference.name} != {qpy.name}\n{reference}\n{qpy}"
sys.stderr.write(msg)
sys.exit(2)
if reference.metadata != qpy.metadata:
msg = f"Circuit {count} metadata mismatch: {reference.metadata} != {qpy.metadata}"
sys.stderr.write(msg)
sys.exit(3)
def generate_qpy(qpy_files):
"""Generate qpy files from reference circuits."""
for path, circuits in qpy_files.items():
with open(path, "wb") as fd:
dump(circuits, fd)
def load_qpy(qpy_files, version_parts):
"""Load qpy circuits from files and compare to reference circuits."""
for path, circuits in qpy_files.items():
print(f"Loading qpy file: {path}")
with open(path, "rb") as fd:
qpy_circuits = load(fd)
equivalent = path in {"open_controlled_gates.qpy", "controlled_gates.qpy"}
for i, circuit in enumerate(circuits):
bind = None
if path == "parameterized.qpy":
bind = [1, 2]
elif path == "param_phase.qpy":
if i == 0:
bind = [1, 2]
else:
bind = [1]
elif path == "parameter_vector.qpy":
bind = np.linspace(1.0, 2.0, 22)
elif path == "parameter_vector_expression.qpy":
bind = np.linspace(1.0, 2.0, 15)
assert_equal(
circuit, qpy_circuits[i], i, version_parts, bind=bind, equivalent=equivalent
)
def _main():
parser = argparse.ArgumentParser(description="Test QPY backwards compatibility")
parser.add_argument("command", choices=["generate", "load"])
parser.add_argument(
"--version",
"-v",
help=(
"Optionally specify the version being tested. "
"This will enable additional circuit features "
"to test generating and loading QPY."
),
)
args = parser.parse_args()
# Terra 0.18.0 was the first release with QPY, so that's the default.
version_parts = (0, 18, 0)
if args.version:
version_match = re.search(VERSION_PATTERN, args.version, re.VERBOSE | re.IGNORECASE)
version_parts = tuple(int(x) for x in version_match.group("release").split("."))
qpy_files = generate_circuits(version_parts)
if args.command == "generate":
generate_qpy(qpy_files)
else:
load_qpy(qpy_files, version_parts)
if __name__ == "__main__":
_main()