Skip to content

Commit

Permalink
Fix broken codes (#1465)
Browse files Browse the repository at this point in the history
### Summary

There were several changes in dependent packages that broke unit test of
this package. This PR fixes broken code.



### Details and comments

188eb5c,
83fecce

After Qiskit >= 1.2, a protected member `_parameter_table` of the
QuantumCircuit is managed in the Rust domain as a part of `CircuitData`
and we cannot directly touch this object from Python, though we should
continue to support <= 1.1. These commits add attribute check before
copying the table.
(edit)

f372eb3
qiskit-ibm-runtime 0.24 was released and deprecation for V1 backends
were added. We should switch to V2 backends. Also some property value
has been changed in one of our reference backends.

ba2bdce

Again, due to the data model update of QuantumCircuit in Qiskit core, a
custom circuit equality function used in a unit test was broken. This
commit changes approach of the test so that it doesn't need to rely on
the custom function.
  • Loading branch information
nkanazawa1989 authored Aug 13, 2024
1 parent b07645f commit 48ffd3b
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 151 deletions.
6 changes: 3 additions & 3 deletions docs/manuals/characterization/stark_experiment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,10 @@ by a variant of the Hahn-echo pulse sequence [5]_.

from qiskit_experiments.library import StarkRamseyXY
from qiskit import schedule, pulse
from qiskit_ibm_runtime.fake_provider import FakeHanoi
from qiskit_ibm_runtime.fake_provider import FakeHanoiV2
from qiskit.visualization.pulse_v2 import IQXSimple

backend = FakeHanoi()
backend = FakeHanoiV2()
exp = StarkRamseyXY(
physical_qubits=[0],
backend=backend,
Expand All @@ -169,7 +169,7 @@ by a variant of the Hahn-echo pulse sequence [5]_.
"formatter.label_offset.pulse_name": 0.1,
"formatter.text_size.annotate": 14,
}
ram_x_schedule.draw(time_range=(0, 1600), style=IQXSimple(**opt), backend=backend)
ram_x_schedule.draw(time_range=(0, 1600), style=IQXSimple(**opt))
The qubit is initialized in the :math:`Y`-eigenstate with the first half-pi pulse.
This state may be visualized by a Bloch vector located on the equator of the Bloch sphere,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,9 @@ def _transpile_clifford_circuit(

def _decompose_clifford_ops(circuit: QuantumCircuit) -> QuantumCircuit:
# Simplified QuantumCircuit.decompose, which decomposes only Clifford ops
# Note that the resulting circuit depends on the input circuit,
# that means the changes on the input circuit may affect the resulting circuit.
# For example, the resulting circuit shares the parameter_table of the input circuit,
res = circuit.copy_empty_like()
res._parameter_table = circuit._parameter_table
if hasattr(circuit, "_parameter_table"):
res._parameter_table = circuit._parameter_table
for inst in circuit:
if inst.operation.name.startswith("Clifford"): # Decompose
rule = inst.operation.definition.data
Expand Down Expand Up @@ -89,7 +87,8 @@ def _apply_qubit_layout(circuit: QuantumCircuit, physical_qubits: Sequence[int])
for reg in circuit.cregs:
res.add_register(reg)
_circuit_compose(res, circuit, qubits=physical_qubits)
res._parameter_table = circuit._parameter_table
if hasattr(circuit, "_parameter_table"):
res._parameter_table = circuit._parameter_table
return res


Expand Down
99 changes: 38 additions & 61 deletions test/framework/test_backend_timing.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,51 +28,44 @@ class TestBackendTiming(QiskitExperimentsTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()

cls.acquire_alignment = 16
cls.dt = 1 / 4.5e9
cls.granularity = 16
cls.min_length = 64
cls.pulse_alignment = 1

def setUp(self):
super().setUp()
# Creating a complete fake backend is difficult so we use one from
# qiskit. Just to be safe, we check that the properties we care about
# for these tests are never changed from what the tests assume.
backend = FakeNairobiV2()
target = backend.target
assumptions = (
(abs(target.dt * 4.5e9 - 1) < 1e-6)
and target.acquire_alignment == 16
and target.pulse_alignment == 1
and target.min_length == 64
and target.granularity == 16
)
if not assumptions: # pragma: no cover
raise ValueError("FakeNairobiV2 properties have changed!")

cls.acquire_alignment = target.acquire_alignment
cls.dt = target.dt
cls.granularity = target.granularity
cls.min_length = target.min_length
cls.pulse_alignment = target.pulse_alignment
# qiskit. Just to be safe, we override hardware properties
# with the values assumed for the unit tests.
self.backend = FakeNairobiV2()
self.backend.target.dt = self.dt
self.backend.target.acquire_alignment = self.acquire_alignment
self.backend.target.pulse_alignment = self.pulse_alignment
self.backend.target.min_length = self.min_length
self.backend.target.granularity = self.granularity

@data((True, "s"), (False, "dt"))
@unpack
def test_delay_unit(self, null_dt, result):
"""Test delay unit matches dt"""
backend = FakeNairobiV2()
if null_dt:
backend.target.dt = None
timing = BackendTiming(backend)
self.backend.target.dt = None
timing = BackendTiming(self.backend)
self.assertEqual(timing.delay_unit, result)

def test_round_delay_args(self):
"""Test argument checking in round_delay"""
backend = FakeNairobiV2()
timing = BackendTiming(backend)
timing = BackendTiming(self.backend)
with self.assertRaises(QiskitError):
timing.round_delay(time=self.dt * 16, samples=16)
with self.assertRaises(QiskitError):
timing.round_delay()

def test_round_pulse_args(self):
"""Test argument checking in round_pulse"""
backend = FakeNairobiV2()
timing = BackendTiming(backend)
timing = BackendTiming(self.backend)
with self.assertRaises(QiskitError):
timing.round_pulse(time=self.dt * 64, samples=64)
with self.assertRaises(QiskitError):
Expand All @@ -84,25 +77,22 @@ def test_round_delay(self, samples_in, samples_out):
"""Test delay calculation with time input"""
time = self.dt * samples_in

backend = FakeNairobiV2()
timing = BackendTiming(backend)
timing = BackendTiming(self.backend)
self.assertEqual(timing.round_delay(time=time), samples_out)

def test_round_delay_no_dt(self):
"""Test delay when dt is None"""
time = self.dt * 16

backend = FakeNairobiV2()
backend.target.dt = None
timing = BackendTiming(backend)
self.backend.target.dt = None
timing = BackendTiming(self.backend)
self.assertEqual(timing.round_delay(time=time), time)

@data([14, 16], [16, 16], [18, 16], [64.5, 64])
@unpack
def test_round_delay_samples_in(self, samples_in, samples_out):
"""Test delay calculation with samples input"""
backend = FakeNairobiV2()
timing = BackendTiming(backend)
timing = BackendTiming(self.backend)
self.assertEqual(timing.round_delay(samples=samples_in), samples_out)

@data([12, 64], [65, 64], [79, 80], [83, 80])
Expand All @@ -111,34 +101,30 @@ def test_round_pulse(self, samples_in, samples_out):
"""Test round pulse calculation with time input"""
time = self.dt * samples_in

backend = FakeNairobiV2()
timing = BackendTiming(backend)
timing = BackendTiming(self.backend)
self.assertEqual(timing.round_pulse(time=time), samples_out)

@data([12, 64], [65, 64], [79, 80], [83, 80], [80.5, 80])
@unpack
def test_round_pulse_samples_in(self, samples_in, samples_out):
"""Test round pulse calculation with samples input"""
backend = FakeNairobiV2()
timing = BackendTiming(backend)
timing = BackendTiming(self.backend)
self.assertEqual(timing.round_pulse(samples=samples_in), samples_out)

def test_delay_time(self):
"""Test delay_time calculation"""
time_in = self.dt * 16.1
time_out = self.dt * 16

backend = FakeNairobiV2()
timing = BackendTiming(backend)
timing = BackendTiming(self.backend)
self.assertAlmostEqual(timing.delay_time(time=time_in), time_out, delta=1e-6 * self.dt)

def test_delay_time_samples_in(self):
"""Test delay_time calculation"""
samples_in = 16.1
time_out = self.dt * 16

backend = FakeNairobiV2()
timing = BackendTiming(backend)
timing = BackendTiming(self.backend)
self.assertAlmostEqual(
timing.delay_time(samples=samples_in), time_out, delta=1e-6 * self.dt
)
Expand All @@ -148,36 +134,32 @@ def test_delay_time_no_dt(self):
time_in = self.dt * 16.1
time_out = time_in

backend = FakeNairobiV2()
backend.target.dt = None
timing = BackendTiming(backend)
self.backend.target.dt = None
timing = BackendTiming(self.backend)
self.assertAlmostEqual(timing.delay_time(time=time_in), time_out, delta=1e-6 * self.dt)

def test_pulse_time(self):
"""Test pulse_time calculation"""
time_in = self.dt * 85.1
time_out = self.dt * 80

backend = FakeNairobiV2()
timing = BackendTiming(backend)
timing = BackendTiming(self.backend)
self.assertAlmostEqual(timing.pulse_time(time=time_in), time_out, delta=1e-6 * self.dt)

def test_pulse_time_samples_in(self):
"""Test pulse_time calculation"""
samples_in = 85.1
time_out = self.dt * 80

backend = FakeNairobiV2()
timing = BackendTiming(backend)
timing = BackendTiming(self.backend)
self.assertAlmostEqual(
timing.pulse_time(samples=samples_in), time_out, delta=1e-6 * self.dt
)

def test_round_pulse_no_dt_error(self):
"""Test methods that don't work when dt is None raise exceptions"""
backend = FakeNairobiV2()
backend.target.dt = None
timing = BackendTiming(backend)
self.backend.target.dt = None
timing = BackendTiming(self.backend)

time = self.dt * 81

Expand All @@ -186,19 +168,14 @@ def test_round_pulse_no_dt_error(self):

def test_unexpected_pulse_alignment(self):
"""Test that a weird pulse_alignment parameter is caught"""
backend = FakeNairobiV2()
backend.target.pulse_alignment = 33
timing = BackendTiming(backend)
self.backend.target.pulse_alignment = 33
timing = BackendTiming(self.backend)
with self.assertRaises(QiskitError):
timing.round_pulse(samples=81)

def test_unexpected_acquire_alignment(self):
"""Test that a weird acquire_alignment parameter is caught"""
backend = FakeNairobiV2()
try:
backend.target.acquire_alignment = 33
except AttributeError:
backend.target.aquire_alignment = 33
timing = BackendTiming(backend)
self.backend.target.acquire_alignment = 33
timing = BackendTiming(self.backend)
with self.assertRaises(QiskitError):
timing.round_pulse(samples=81)
106 changes: 37 additions & 69 deletions test/library/characterization/test_cross_resonance_hamiltonian.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@

"""Spectroscopy tests."""
from test.base import QiskitExperimentsTestCase
from test.extended_equality import is_equivalent
import functools
import io
from unittest.mock import patch

import numpy as np
from ddt import ddt, data, unpack

from qiskit import QuantumCircuit, pulse, qpy, quantum_info as qi
from qiskit.circuit import Gate

# TODO: remove old path after we stop supporting the relevant version of Qiskit
try:
Expand All @@ -34,36 +36,6 @@
from qiskit_experiments.library.characterization import cr_hamiltonian


def is_equivalent_circuit(circ1: QuantumCircuit, circ2: QuantumCircuit) -> bool:
"""
Check if two circuits are structurally the same.
We use it due to the field 'operation' under 'circ.data[i]' wich its '__qe__'
method isn't good for reconstructed circuits (by using qpy) with custom pulse gates.
"""
check = (
is_equivalent(circ1.calibrations, circ2.calibrations)
and is_equivalent(circ1.qregs, circ2.qregs)
and is_equivalent(circ1.qubits, circ2.qubits)
)

for data1, data2 in zip(circ1.data, circ2.data):
circ1_op = data1.operation
circ2_op = data2.operation
check = (
check
and is_equivalent(data1.clbits, data2.clbits)
and is_equivalent(data1.qubits, data2.qubits)
and is_equivalent(circ1_op.definition, circ2_op.definition)
and is_equivalent(circ1_op.name, circ2_op.name)
and is_equivalent(circ1_op.params, circ2_op.params)
and is_equivalent(circ1_op.unit, circ2_op.unit)
and is_equivalent(circ1_op.num_clbits, circ2_op.num_clbits)
and is_equivalent(circ1_op.num_qubits, circ2_op.num_qubits)
)

return check


class SimulatableCRGate(HamiltonianGate):
"""Hamiltonian Gate for simulation."""

Expand Down Expand Up @@ -302,48 +274,44 @@ def test_circuit_serialization(self):
"""Test generated circuits."""
backend = FakeBogotaV2()

expr = cr_hamiltonian.CrossResonanceHamiltonian(
physical_qubits=(0, 1),
amp=0.1,
sigma=64,
risefall=2,
)
expr.backend = backend

with pulse.build(default_alignment="left", name="cr") as _:
pulse.play(
pulse.GaussianSquare(
duration=1256,
amp=0.1,
sigma=64,
width=1000,
),
pulse.ControlChannel(0),
with patch.object(
cr_hamiltonian.CrossResonanceHamiltonian.CRPulseGate,
"base_class",
Gate,
):
# Monkey patching the Instruction.base_class property of the CRPulseGate.
# QPY loader is not aware of Gate subclasses defined outside Qiskit core,
# and a Gate subclass instance is reconstructed as a Gate class instance.
# This results in the failure in comparison of structurally same circuits.
# In this context, CRPulseGate looks like a Gate class.
expr = cr_hamiltonian.CrossResonanceHamiltonian(
physical_qubits=(0, 1),
amp=0.1,
sigma=64,
risefall=2,
)
pulse.delay(1256, pulse.DriveChannel(0))
pulse.delay(1256, pulse.DriveChannel(1))
expr.backend = backend

width_sec = 1000 * backend.dt
cr_gate = cr_hamiltonian.CrossResonanceHamiltonian.CRPulseGate(width=width_sec)
circuits = expr._transpiled_circuits()
width_sec = 1000 * backend.dt
cr_gate = cr_hamiltonian.CrossResonanceHamiltonian.CRPulseGate(width=width_sec)
circuits = expr._transpiled_circuits()

x0_circ = QuantumCircuit(2, 1)
x0_circ.append(cr_gate, [0, 1])
x0_circ.rz(np.pi / 2, 1)
x0_circ.sx(1)
x0_circ.measure(1, 0)
x0_circ = QuantumCircuit(2, 1)
x0_circ.append(cr_gate, [0, 1])
x0_circ.rz(np.pi / 2, 1)
x0_circ.sx(1)
x0_circ.measure(1, 0)

circuits.append(x0_circ)
circuits.append(x0_circ)

with io.BytesIO() as buff:
qpy.dump(circuits, buff)
buff.seek(0)
serialized_data = buff.read()
with io.BytesIO() as buff:
qpy.dump(circuits, buff)
buff.seek(0)
serialized_data = buff.read()

with io.BytesIO() as buff:
buff.write(serialized_data)
buff.seek(0)
decoded = qpy.load(buff)
with io.BytesIO() as buff:
buff.write(serialized_data)
buff.seek(0)
decoded = qpy.load(buff)

for circ1, circ2 in zip(circuits, decoded):
self.assertTrue(is_equivalent_circuit(circ1, circ2))
self.assertListEqual(circuits, decoded)
Loading

0 comments on commit 48ffd3b

Please sign in to comment.