Skip to content

Commit

Permalink
Raise if abstract circuits are used (#1437)
Browse files Browse the repository at this point in the history
* support dynamic circuits

* raise for abstract circuits

* lint

* fix test

* remove hard coded instructions

* check target is not none

* lint

* fix test

* check for error not warning

* bypass simulator
  • Loading branch information
jyu00 authored Mar 4, 2024
1 parent 2666259 commit ee086b0
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 46 deletions.
6 changes: 4 additions & 2 deletions qiskit_ibm_runtime/base_primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,12 @@ def _run_primitive(self, primitive_inputs: Dict, user_kwargs: Dict) -> RuntimeJo
Submitted job.
"""
if (
self._backend
self._backend # pylint: disable=too-many-boolean-expressions
and isinstance(self._backend, IBMBackend)
and isinstance(self._backend.service, QiskitRuntimeService)
and hasattr(self._backend, "target")
and not self._backend.simulator
and getattr(self._backend, "target", None)
and self._service._channel_strategy != "q-ctrl"
):
validate_isa_circuits(primitive_inputs["circuits"], self._backend.target)

Expand Down
12 changes: 4 additions & 8 deletions qiskit_ibm_runtime/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import os
import re
import hashlib
import warnings
from queue import Queue
from threading import Condition
from typing import List, Optional, Any, Dict, Union, Tuple, Sequence
Expand Down Expand Up @@ -74,17 +73,14 @@ def validate_isa_circuits(circuits: Sequence[QuantumCircuit], target: Target) ->
for circuit in circuits:
message = is_isa_circuit(circuit, target)
if message:
warnings.warn(
raise IBMInputValueError(
message
+ " Circuits that do not match the target hardware definition will no longer be "
"supported after March 1, 2024. See the transpilation documentation "
+ " Circuits that do not match the target hardware definition are no longer "
"supported after March 4, 2024. See the transpilation documentation "
"(https://docs.quantum.ibm.com/transpile) for instructions to transform circuits and "
"the primitive examples (https://docs.quantum.ibm.com/run/primitives-examples) to see "
"this coupled with operator transformations.",
DeprecationWarning,
stacklevel=6,
"this coupled with operator transformations."
)
break


def validate_job_tags(job_tags: Optional[List[str]]) -> None:
Expand Down
10 changes: 10 additions & 0 deletions releasenotes/notes/isa-circuit-required-ed361bd65cef5ed8.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
upgrade:
- |
Circuits that do not match the target hardware definition are no longer
supported by Qiskit Runtime primitives, unless ``channel_strategy="q-ctrl"``
is used. See the transpilation documentation
(https://docs.quantum.ibm.com/transpile) for instructions to transform
circuits and the primitive examples
(https://docs.quantum.ibm.com/run/primitives-examples) to see
this coupled with operator transformations.
90 changes: 76 additions & 14 deletions test/unit/test_ibm_primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@
from unittest.mock import MagicMock, patch
from dataclasses import asdict
from typing import Dict
import warnings

from ddt import data, ddt
from qiskit import transpile, pulse
from qiskit.circuit import QuantumCircuit
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.pulse.library import Gaussian
from qiskit.quantum_info import SparsePauliOp
from qiskit.providers.models.backendconfiguration import QasmBackendConfiguration

from qiskit_ibm_runtime.fake_provider import FakeManila
from qiskit_ibm_runtime.fake_provider import FakeManila, FakeSherbrooke
from qiskit_ibm_runtime import (
Sampler,
Estimator,
Expand All @@ -36,6 +34,7 @@
)
from qiskit_ibm_runtime.ibm_backend import IBMBackend
from qiskit_ibm_runtime.utils.default_session import _DEFAULT_SESSION
from qiskit_ibm_runtime.exceptions import IBMInputValueError

from ..ibm_test_case import IBMTestCase
from ..utils import (
Expand Down Expand Up @@ -866,7 +865,7 @@ def test_abstract_circuits(self, primitive):
else:
circ.measure_all()

with self.assertWarnsRegex(DeprecationWarning, "target hardware"):
with self.assertRaisesRegex(IBMInputValueError, "target hardware"):
inst.run(**run_input)

@data(Sampler, Estimator)
Expand All @@ -876,7 +875,6 @@ def test_abstract_circuits_backend_no_coupling_map(self, primitive):
config = FakeManila().configuration().to_dict()
for gate in config["gates"]:
gate.pop("coupling_map", None)
config = QasmBackendConfiguration.from_dict(config)
backend = get_mocked_backend(configuration=config)

inst = primitive(backend=backend)
Expand All @@ -889,10 +887,7 @@ def test_abstract_circuits_backend_no_coupling_map(self, primitive):
else:
transpiled.measure_all()

with warnings.catch_warnings(record=True) as warns:
warnings.simplefilter("always")
inst.run(**run_input)
self.assertFalse(warns)
inst.run(**run_input)

@data(Sampler, Estimator)
def test_pulse_gates_is_isa(self, primitive):
Expand All @@ -912,10 +907,60 @@ def test_pulse_gates_is_isa(self, primitive):
else:
circuit.measure_all()

with warnings.catch_warnings(record=True) as warns:
warnings.simplefilter("always")
inst.run(**run_input)
self.assertFalse(warns)
inst.run(**run_input)

@data(Sampler, Estimator)
def test_dynamic_circuit_is_isa(self, primitive):
"""Test passing dynmaic circuits is considered ISA."""
# pylint: disable=not-context-manager
# pylint: disable=invalid-name
sherbrooke = FakeSherbrooke()
config = sherbrooke._get_conf_dict_from_json()
config["supported_instructions"] += ["for_loop", "switch_case", "while_loop"]

backend = get_mocked_backend(
configuration=config,
properties=sherbrooke._set_props_dict_from_json(),
defaults=sherbrooke._set_defs_dict_from_json(),
)

inst = primitive(backend=backend)

qubits = QuantumRegister(3)
clbits = ClassicalRegister(3)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1, q2) = qubits
(c0, c1, c2) = clbits

circuit.x(q0)
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)):
circuit.x(q0)

circuit.measure(q1, c1)
with circuit.switch(c1) as case:
with case(0):
circuit.x(q0)
with case(1):
circuit.x(q1)

circuit.measure(q1, c1)
circuit.measure(q2, c2)
with circuit.while_loop((clbits, 0b111)):
circuit.rz(1.5, q1)
circuit.rz(1.5, q2)
circuit.measure(q1, c1)
circuit.measure(q2, c2)

with circuit.for_loop(range(2)) as _:
circuit.x(q0)

circuit = transpile(circuit, backend=backend)
run_input = {"circuits": circuit}
if isinstance(inst, Estimator):
run_input["observables"] = SparsePauliOp("ZZZ").apply_layout(circuit.layout)

inst.run(**run_input)

def _update_dict(self, dict1, dict2):
for key, val in dict1.items():
Expand Down Expand Up @@ -995,3 +1040,20 @@ def test_qctrl_unsupported_values_for_options(self):
_ = inst.run(self.qx, observables=self.obs, **bad_opt)

self.assertIn(expected_message, str(exc.exception))

@data(Sampler, Estimator)
def test_qctrl_abstract_circuit(self, primitive):
"""Test q-ctrl can still accept abstract circuits."""
backend = get_mocked_backend()
backend._service._channel_strategy = "q-ctrl"
inst = primitive(backend=backend)

circ = QuantumCircuit(3, 3)
circ.cx(0, 2)
run_input = {"circuits": circ}
if isinstance(inst, Estimator):
run_input["observables"] = SparsePauliOp("ZZZ")
else:
circ.measure_all()

inst.run(**run_input)
24 changes: 11 additions & 13 deletions test/unit/test_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@

"""Tests for sampler class."""

from qiskit_ibm_runtime import Sampler, Session
from qiskit import transpile

from qiskit_ibm_runtime import Sampler

from ..ibm_test_case import IBMTestCase
from ..utils import bell
from .mock.fake_runtime_service import FakeRuntimeService
from ..utils import bell, get_mocked_backend


class TestSampler(IBMTestCase):
Expand All @@ -28,14 +29,11 @@ def test_unsupported_values_for_sampler_options(self):
{"resilience_level": 2, "optimization_level": 3},
{"optimization_level": 4, "resilience_level": 1},
]
backend = get_mocked_backend()
circuit = transpile(bell(), backend=backend)

with Session(
service=FakeRuntimeService(channel="ibm_quantum", token="abc"),
backend="common_backend",
) as session:
circuit = bell()
for bad_opt in options_bad:
inst = Sampler(session=session)
with self.assertRaises(ValueError) as exc:
_ = inst.run(circuit, **bad_opt)
self.assertIn(list(bad_opt.keys())[0], str(exc.exception))
for bad_opt in options_bad:
sampler = Sampler(backend=backend)
with self.assertRaises(ValueError) as exc:
_ = sampler.run(circuit, **bad_opt)
self.assertIn(list(bad_opt.keys())[0], str(exc.exception))
25 changes: 16 additions & 9 deletions test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
from qiskit.qobj import QasmQobj
from qiskit.providers.jobstatus import JOB_FINAL_STATES, JobStatus
from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit.providers.models import BackendStatus, BackendProperties, BackendConfiguration
from qiskit.providers.models import (
BackendStatus,
BackendProperties,
BackendConfiguration,
)
from qiskit.providers.backend import Backend

from qiskit_ibm_runtime.hub_group_project import HubGroupProject
Expand Down Expand Up @@ -277,22 +281,25 @@ def create_faulty_backend(


def get_mocked_backend(
name: str = "ibm_gotham", configuration: Optional[BackendConfiguration] = None
name: str = "ibm_gotham",
configuration: Optional[Dict] = None,
properties: Optional[Dict] = None,
defaults: Optional[Dict] = None,
) -> IBMBackend:
"""Return a mock backend."""

def _noop(*args, **kwargs): # pylint: disable=unused-argument
return None

mock_service = mock.MagicMock(spec=QiskitRuntimeService)
mock_service._channel_strategy = None
mock_api_client = mock.MagicMock()

if not configuration:
configuration = FakeManila().configuration()
configuration = (
FakeManila().configuration()
if configuration is None
else BackendConfiguration.from_dict(configuration)
)

mock_api_client.backend_properties = _noop
mock_api_client.backend_pulse_defaults = _noop
mock_api_client.backend_properties = lambda *args, **kwargs: properties
mock_api_client.backend_pulse_defaults = lambda *args, **kwargs: defaults
mock_backend = IBMBackend(
configuration=configuration, service=mock_service, api_client=mock_api_client
)
Expand Down

0 comments on commit ee086b0

Please sign in to comment.