Skip to content

Commit

Permalink
Replace FakeOpenPulse2Q with GenericBackendV2
Browse files Browse the repository at this point in the history
  • Loading branch information
wshanks committed Jun 6, 2024
1 parent 662f19b commit 220a5ba
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 85 deletions.
75 changes: 14 additions & 61 deletions qiskit_experiments/test/mock_iq_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@
# that they have been altered from the originals.

"""A mock IQ backend for testing."""
import datetime
from abc import abstractmethod
from typing import Sequence, List, Tuple, Dict, Union, Any

import numpy as np

from qiskit import QuantumCircuit
from qiskit.circuit.library import XGate, SXGate
from qiskit.result import Result
from qiskit.providers import BackendV2, Provider, convert_to_target
from qiskit.providers.fake_provider import FakeOpenPulse2Q
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.qobj.utils import MeasLevel

from qiskit_experiments.exceptions import QiskitError
Expand All @@ -35,59 +32,16 @@
)


class FakeOpenPulse2QV2(BackendV2):
"""BackendV2 conversion of qiskit.providers.fake_provider.FakeOpenPulse2Q"""

def __init__(
self,
provider: Provider = None,
name: str = None,
description: str = None,
online_date: datetime.datetime = None,
backend_version: str = None,
**fields,
):
super().__init__(provider, name, description, online_date, backend_version, **fields)

backend_v1 = FakeOpenPulse2Q()
# convert_to_target requires the description attribute
backend_v1._configuration.description = "A fake test backend with pulse defaults"

self._target = convert_to_target(
backend_v1.configuration(),
backend_v1.properties(),
backend_v1.defaults(),
add_delay=True,
)
# See commented out defaults() method below
self._defaults = backend_v1._defaults

# This method is not defined in the base class as we would like to avoid
# relying on it as much as necessary. Individual tests should add it when
# necessary.
# def defaults(self):
# """Pulse defaults"""
# return self._defaults

@property
def max_circuits(self):
return 300

@property
def target(self):
return self._target


class MockRestlessBackend(FakeOpenPulse2QV2):
class MockRestlessBackend(GenericBackendV2):
"""An abstract backend for testing that can mock restless data."""

def __init__(self, rng_seed: int = 0):
"""
Initialize the backend.
"""
self._rng = np.random.default_rng(rng_seed)
self.__rng = np.random.default_rng(rng_seed)
self._precomputed_probabilities = None
super().__init__()
super().__init__(num_qubits=2, calibrate_instructions=True, seed=rng_seed)

@classmethod
def _default_options(cls):
Expand Down Expand Up @@ -147,7 +101,7 @@ def run(self, run_input, **options):
for circ_idx, _ in enumerate(run_input):
probs = self._precomputed_probabilities[(circ_idx, prev_outcome)]
# Generate the next shot dependent on the pre-computed probabilities.
outcome = self._rng.choice(state_strings, p=probs)
outcome = self.__rng.choice(state_strings, p=probs)
# Append the single shot to the memory of the corresponding circuit.
sorted_memory[circ_idx]["memory"].append(hex(int(outcome, 2)))

Expand Down Expand Up @@ -192,9 +146,6 @@ def __init__(
self._angle_per_gate = angle_per_gate
super().__init__(rng_seed=rng_seed)

self.target.add_instruction(SXGate(), properties={(0,): None})
self.target.add_instruction(XGate(), properties={(0,): None})

def _compute_outcome_probabilities(self, circuits: List[QuantumCircuit]):
"""Compute the probabilities of being in the excited state or
ground state for all circuits."""
Expand All @@ -219,7 +170,7 @@ def _compute_outcome_probabilities(self, circuits: List[QuantumCircuit]):
self._precomputed_probabilities[(idx, "1")] = [prob_1, prob_0]


class MockIQBackend(FakeOpenPulse2QV2):
class MockIQBackend(GenericBackendV2):
"""A mock backend for testing with IQ data."""

def __init__(
Expand All @@ -238,9 +189,10 @@ def __init__(
"""

self._experiment_helper = experiment_helper
self._rng = np.random.default_rng(rng_seed)
# Can not be called _rng because GenericBackendV2 sets a _rng attribute
self.__rng = np.random.default_rng(rng_seed)

super().__init__()
super().__init__(num_qubits=2, calibrate_instructions=True, seed=rng_seed)

@classmethod
def _default_options(cls):
Expand Down Expand Up @@ -323,7 +275,7 @@ def _get_normal_samples_for_shot(
Returns:
Ndarray: A numpy array with values that were produced from normal distribution.
"""
samples = [self._rng.normal(0, 1, size=1) for qubit in qubits]
samples = [self.__rng.normal(0, 1, size=1) for qubit in qubits]
# we squeeze the second dimension because samples is List[qubit_number][0][0\1] = I\Q
# and we want to change it to be List[qubit_number][0\1]
return np.squeeze(np.array(samples), axis=1)
Expand Down Expand Up @@ -396,7 +348,7 @@ def _draw_iq_shots(
shot_num = 0

for output_number, number_of_occurrences in enumerate(
self._rng.multinomial(shots, prob, size=1)[0]
self.__rng.multinomial(shots, prob, size=1)[0]
):
state_str = str(format(output_number, "b").zfill(len(circ_qubits)))
for _ in range(number_of_occurrences):
Expand Down Expand Up @@ -451,7 +403,7 @@ def _generate_data(

if meas_level == MeasLevel.CLASSIFIED:
counts = {}
results = self._rng.multinomial(shots, prob_arr, size=1)[0]
results = self.__rng.multinomial(shots, prob_arr, size=1)[0]
for result, num_occurrences in enumerate(results):
result_in_str = str(format(result, "b").zfill(output_length))
counts[result_in_str] = num_occurrences
Expand Down Expand Up @@ -551,6 +503,7 @@ def __init__(
helper classes for each experiment.
rng_seed: The random seed value.
"""
self.__rng = np.random.default_rng(rng_seed)
super().__init__(experiment_helper, rng_seed)

@property
Expand Down Expand Up @@ -634,7 +587,7 @@ def _parallel_draw_iq_shots(
shot_num = 0

for output_number, number_of_occurrences in enumerate(
self._rng.multinomial(shots, prob, size=1)[0]
self.__rng.multinomial(shots, prob, size=1)[0]
):
state_str = str(format(output_number, "b").zfill(len(qubits)))
for _ in range(number_of_occurrences):
Expand Down
4 changes: 2 additions & 2 deletions test/library/calibration/test_drag.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def setUp(self):
pulse.play(Drag(duration=160, amp=0.208519, sigma=40, beta=beta), DriveChannel(0))

self.x_plus = xp
self.test_tol = 0.1
self.test_tol = 0.25

@data(
(None, None, None),
Expand All @@ -63,7 +63,7 @@ def test_end_to_end(self, freq, betas, p0_opt):
backend = MockIQBackend(drag_experiment_helper)

drag = RoughDrag([1], self.x_plus)
drag.set_run_options(shots=200)
drag.set_run_options(shots=500)

if betas is not None:
drag.set_experiment_options(betas=betas)
Expand Down
12 changes: 4 additions & 8 deletions test/library/calibration/test_fine_amplitude.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@ def test_end_to_end_under_rotation(self, pi_ratio):

error = -np.pi * pi_ratio
backend = MockIQBackend(FineAmpHelper(error, np.pi, "x"))
backend.target.add_instruction(XGate(), properties={(0,): None})
backend.target.add_instruction(SXGate(), properties={(0,): None})

expdata = amp_exp.run(backend)
# Needs extra shots to avoid chisq > 3
expdata = amp_exp.run(backend, shots=1600)
self.assertExperimentDone(expdata)
result = expdata.analysis_results("d_theta")
d_theta = result.value.n
Expand All @@ -67,8 +66,6 @@ def test_end_to_end_over_rotation(self, pi_ratio):

error = np.pi * pi_ratio
backend = MockIQBackend(FineAmpHelper(error, np.pi, "x"))
backend.target.add_instruction(XGate(), properties={(0,): None})
backend.target.add_instruction(SXGate(), properties={(0,): None})
expdata = amp_exp.run(backend)
self.assertExperimentDone(expdata)
result = expdata.analysis_results("d_theta")
Expand Down Expand Up @@ -99,7 +96,8 @@ def test_end_to_end(self, pi_ratio):
backend = MockIQBackend(FineAmpHelper(error, np.pi / 2, "szx"))
backend.target.add_instruction(Gate("szx", 2, []), properties={(0, 1): None})

expdata = amp_exp.run(backend)
# Needs extra shots to avoid chisq > 3
expdata = amp_exp.run(backend, shots=1600)
self.assertExperimentDone(expdata)
result = expdata.analysis_results("d_theta")
d_theta = result.value.n
Expand Down Expand Up @@ -218,8 +216,6 @@ def setUp(self):
library = FixedFrequencyTransmon()

self.backend = MockIQBackend(FineAmpHelper(-np.pi * 0.07, np.pi, "xp"))
self.backend.target.add_instruction(SXGate(), properties={(0,): None})
self.backend.target.add_instruction(XGate(), properties={(0,): None})
self.cals = Calibrations.from_backend(self.backend, libraries=[library])

def test_cal_options(self):
Expand Down
2 changes: 1 addition & 1 deletion test/library/calibration/test_ramsey_xy.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def test_end_to_end(self, freq_shift: float):
This test also checks that we can pickup frequency shifts with different signs.
"""
test_tol = 0.03
test_tol = 0.05
abs_tol = max(1e3, abs(freq_shift) * test_tol)

exp_helper = RamseyXYHelper()
Expand Down
10 changes: 0 additions & 10 deletions test/library/characterization/test_qubit_spectroscopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import numpy as np

from qiskit.qobj.utils import MeasLevel
from qiskit.circuit.library import XGate
from qiskit_ibm_runtime.fake_provider import FakeWashingtonV2
from qiskit_experiments.framework import ParallelExperiment

Expand All @@ -42,7 +41,6 @@ def test_spectroscopy_end2end_classified(self):
backend = MockIQBackend(
experiment_helper=exp_helper,
)
backend.target.add_instruction(XGate(), properties={(0,): None})

qubit = 1
freq01 = BackendData(backend).drive_freqs[qubit]
Expand Down Expand Up @@ -82,7 +80,6 @@ def test_spectroscopy_end2end_kerneled(self):
backend = MockIQBackend(
experiment_helper=exp_helper,
)
backend.target.add_instruction(XGate(), properties={(0,): None})

qubit = 0
freq01 = BackendData(backend).drive_freqs[qubit]
Expand Down Expand Up @@ -128,7 +125,6 @@ def test_spectroscopy12_end2end_classified(self):
iq_cluster_width=[0.2],
),
)
backend.target.add_instruction(XGate(), properties={(0,): None})
qubit = 0
freq01 = BackendData(backend).drive_freqs[qubit]
frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21)
Expand Down Expand Up @@ -174,7 +170,6 @@ def test_expdata_serialization(self):
backend = MockIQBackend(
experiment_helper=exp_helper,
)
backend.target.add_instruction(XGate(), properties={(0,): None})

qubit = 1
freq01 = BackendData(backend).drive_freqs[qubit]
Expand All @@ -201,7 +196,6 @@ def test_kerneled_expdata_serialization(self):
backend = MockIQBackend(
experiment_helper=exp_helper,
)
backend.target.add_instruction(XGate(), properties={(0,): None})

qubit = 1
freq01 = BackendData(backend).drive_freqs[qubit]
Expand Down Expand Up @@ -231,10 +225,6 @@ def test_parallel_experiment(self):
experiment_helper=None,
rng_seed=0,
)
parallel_backend.target.add_instruction(
XGate(),
properties={(0,): None, (1,): None},
)

# experiment hyper parameters
qubit1 = 0
Expand Down
13 changes: 11 additions & 2 deletions test/library/characterization/test_resonator_spectroscopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

"""Spectroscopy tests for resonator spectroscopy experiment."""

from __future__ import annotations

from test.base import QiskitExperimentsTestCase
from typing import Any, List, Tuple

Expand All @@ -34,6 +36,13 @@
)


class MockDefaults:
"""Just enough qiskit.providers.models.PulseDefaults for ResonatorSpectroscpy"""

def __init__(self, meas_freq_est: list[float]):
self.meas_freq_est = meas_freq_est


class MockIQBackendDefaults(MockIQBackend):
"""MockIQBackend with defaults() method"""

Expand All @@ -45,7 +54,7 @@ def defaults(self):
to Backend classes outside of this test module so that we do not
introduce new dependencies on it.
"""
return self._defaults
return MockDefaults(meas_freq_est=[7e9] * self.num_qubits)


class MockIQParallelBackendDefaults(MockIQParallelBackend):
Expand All @@ -59,7 +68,7 @@ def defaults(self):
to Backend classes outside of this test module so that we do not
introduce new dependencies on it.
"""
return self._defaults
return MockDefaults(meas_freq_est=[7e9] * self.num_qubits)


def data_valid_initial_circuits() -> List[Tuple[Any, str]]:
Expand Down
2 changes: 1 addition & 1 deletion test/library/characterization/test_zz_ramsey.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def compute_probabilities(self, circuits: List[QuantumCircuit]) -> List[Dict[str
freq = (-1 * self.zz_freq) / 2
else:
freq = self.zz_freq / 2
rz, _, _ = next(i for i in circuit.data if i[0].name == "u1")
rz, _, _ = next(i for i in circuit.data if i[0].name == "rz")
phase = float(rz.params[0])

prob1 = 0.5 - 0.5 * np.cos(2 * np.pi * freq * delay + phase)
Expand Down

0 comments on commit 220a5ba

Please sign in to comment.