From 6eb4030462555af9ec50327b0a29d805ff3225d3 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Tue, 19 Nov 2024 13:17:04 -0500 Subject: [PATCH 1/2] Add checks for parameter expressions with gen3-turbo runtime. --- qiskit_ibm_runtime/base_primitive.py | 7 ++++++- qiskit_ibm_runtime/utils/__init__.py | 1 + qiskit_ibm_runtime/utils/utils.py | 12 ++++++++++- qiskit_ibm_runtime/utils/validations.py | 27 +++++++++++++++++++++++- test/unit/test_sampler.py | 28 +++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm_runtime/base_primitive.py b/qiskit_ibm_runtime/base_primitive.py index b150a4ee1..5e35a15ca 100644 --- a/qiskit_ibm_runtime/base_primitive.py +++ b/qiskit_ibm_runtime/base_primitive.py @@ -27,7 +27,11 @@ from .options.utils import merge_options_v2 from .runtime_job_v2 import RuntimeJobV2 from .ibm_backend import IBMBackend -from .utils import validate_isa_circuits, validate_no_dd_with_dynamic_circuits +from .utils import ( + validate_isa_circuits, + validate_no_dd_with_dynamic_circuits, + validate_no_param_expressions_gen3_runtime, +) from .utils.default_session import get_cm_session from .utils.deprecation import issue_deprecation_msg from .utils.utils import is_simulator @@ -170,6 +174,7 @@ def _run(self, pubs: Union[list[EstimatorPub], list[SamplerPub]]) -> RuntimeJobV runtime_options = self._options_class._get_runtime_options(options_dict) validate_no_dd_with_dynamic_circuits([pub.circuit for pub in pubs], self.options) + validate_no_param_expressions_gen3_runtime([pub.circuit for pub in pubs], self.options) if self._backend: for pub in pubs: if getattr(self._backend, "target", None) and not is_simulator(self._backend): diff --git a/qiskit_ibm_runtime/utils/__init__.py b/qiskit_ibm_runtime/utils/__init__.py index 7005a241b..fb33b680c 100644 --- a/qiskit_ibm_runtime/utils/__init__.py +++ b/qiskit_ibm_runtime/utils/__init__.py @@ -33,6 +33,7 @@ validate_no_dd_with_dynamic_circuits, validate_isa_circuits, validate_job_tags, + validate_no_param_expressions_gen3_runtime, ) from .json import RuntimeEncoder, RuntimeDecoder, to_base64_string diff --git a/qiskit_ibm_runtime/utils/utils.py b/qiskit_ibm_runtime/utils/utils.py index 608ee7b84..c8bc62215 100644 --- a/qiskit_ibm_runtime/utils/utils.py +++ b/qiskit_ibm_runtime/utils/utils.py @@ -29,7 +29,7 @@ IAMAuthenticator, ) from ibm_platform_services import ResourceControllerV2 # pylint: disable=import-error -from qiskit.circuit import QuantumCircuit, ControlFlowOp, ParameterExpression +from qiskit.circuit import QuantumCircuit, ControlFlowOp, ParameterExpression, Parameter from qiskit.transpiler import Target from qiskit.providers.backend import BackendV1, BackendV2 from .deprecation import deprecate_function @@ -130,6 +130,16 @@ def are_circuits_dynamic(circuits: List[QuantumCircuit], qasm_default: bool = Tr return False +def has_param_expressions(circuits: List[QuantumCircuit]) -> bool: + """Checks if the input circuits contain `ParameterExpression`s""" + for circuit in circuits: + for instruction in circuit.data: + for p in instruction.operation.params: + if isinstance(p, ParameterExpression) and not isinstance(p, Parameter): + return True + return False + + def get_iam_api_url(cloud_url: str) -> str: """Computes the IAM API URL for the given IBM Cloud URL.""" parsed_url = urlparse(cloud_url) diff --git a/qiskit_ibm_runtime/utils/validations.py b/qiskit_ibm_runtime/utils/validations.py index c2a27df72..8660a923e 100644 --- a/qiskit_ibm_runtime/utils/validations.py +++ b/qiskit_ibm_runtime/utils/validations.py @@ -19,7 +19,11 @@ from qiskit.transpiler import Target from qiskit.primitives.containers.sampler_pub import SamplerPub from qiskit.primitives.containers.estimator_pub import EstimatorPub -from qiskit_ibm_runtime.utils.utils import is_isa_circuit, are_circuits_dynamic +from qiskit_ibm_runtime.utils.utils import ( + is_isa_circuit, + are_circuits_dynamic, + has_param_expressions, +) from qiskit_ibm_runtime.exceptions import IBMInputValueError @@ -127,3 +131,24 @@ def validate_job_tags(job_tags: Optional[List[str]]) -> None: not isinstance(job_tags, list) or not all(isinstance(tag, str) for tag in job_tags) ): raise IBMInputValueError("job_tags needs to be a list of strings.") + + +def validate_no_param_expressions_gen3_runtime( + circuits: List[QuantumCircuit], options: Any +) -> None: + """Validate that when the gen3 runtime is used, no circuit in the pubs contain + ParameterExpressions. + + Args: + circuits: A list of QuantumCircuits + options: The runtime options + """ + if ( + not hasattr(options.experimental, "get") + or options.experimental.get("execution_path", None) != "gen3-turbo" + ): + return + if has_param_expressions(circuits): + raise IBMInputValueError( + "The gen3-turbo runtime currently does not support parameter expressions" + ) diff --git a/test/unit/test_sampler.py b/test/unit/test_sampler.py index 8f94d5222..32e7e2bda 100644 --- a/test/unit/test_sampler.py +++ b/test/unit/test_sampler.py @@ -322,6 +322,34 @@ def test_rzz_validates_only_for_fixed_angles(self): # Should run without an error SamplerV2(backend).run(pubs=[(circ, [0.5])]) + def test_param_expressions_gen3_runtime(self): + """Verify that parameter expressions are not used in combination with the gen3-turbo + execution path.""" + backend = FakeCusco() + x = Parameter("x") + y = Parameter("y") + opts = SamplerOptions(experimental={"execution_path": "gen3-turbo"}) + + with self.subTest("float"): + circ = QuantumCircuit(1, 1) + circ.rz(x, 0) + circ.measure(0, 0) + bound_circ = circ.assign_parameters({x: 0.1}) + # expect no error (parameter is bound to a float) + SamplerV2(backend, opts).run(pubs=[(bound_circ,)]) + # expect no error (simple parameter, not a parameter expression) + SamplerV2(backend, opts).run(pubs=[(circ, [0.2])]) + + with self.subTest("parameter expressions"): + circ = QuantumCircuit(1, 1) + circ.rz(x + 2 * y, 0) + circ.measure(0, 0) + with self.assertRaises(IBMInputValueError): + SamplerV2(backend, opts).run(pubs=[(circ, [0.1, 0.2])]) + # without the gen3-turbo execution path, we expect no error + opts.experimental["execution_path"] = "" + SamplerV2(backend, opts).run(pubs=[(circ, [0.1, 0.2])]) + @data( "classified", "kerneled", From 2228fd8ca1e55eb160e809a41617c0eff35f10b8 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Tue, 19 Nov 2024 14:44:44 -0500 Subject: [PATCH 2/2] Workaround for linting failures. --- test/unit/test_sampler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/test_sampler.py b/test/unit/test_sampler.py index 32e7e2bda..fb8da82cf 100644 --- a/test/unit/test_sampler.py +++ b/test/unit/test_sampler.py @@ -328,6 +328,7 @@ def test_param_expressions_gen3_runtime(self): backend = FakeCusco() x = Parameter("x") y = Parameter("y") + # pylint: disable-next=unexpected-keyword-arg opts = SamplerOptions(experimental={"execution_path": "gen3-turbo"}) with self.subTest("float"): @@ -347,6 +348,7 @@ def test_param_expressions_gen3_runtime(self): with self.assertRaises(IBMInputValueError): SamplerV2(backend, opts).run(pubs=[(circ, [0.1, 0.2])]) # without the gen3-turbo execution path, we expect no error + # pylint: disable-next=unsupported-assignment-operation opts.experimental["execution_path"] = "" SamplerV2(backend, opts).run(pubs=[(circ, [0.1, 0.2])])