From ea620618d07f1d97faccfed181fb357e4bfce143 Mon Sep 17 00:00:00 2001 From: Sankalp Thakur Date: Thu, 23 Jul 2026 10:38:44 +0530 Subject: [PATCH] fix: reject NaN RZZ angles Evaluate parameter expressions before validating their effective RZZ angle so algebraically eliminated NaN inputs remain valid. --- qiskit_ibm_runtime/utils/utils.py | 6 +++--- release-notes/unreleased/2452.bug.rst | 3 +++ test/unit/test_sampler.py | 15 +++++++++++++-- 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 release-notes/unreleased/2452.bug.rst diff --git a/qiskit_ibm_runtime/utils/utils.py b/qiskit_ibm_runtime/utils/utils.py index 1e5653fae7..e278c32c4a 100644 --- a/qiskit_ibm_runtime/utils/utils.py +++ b/qiskit_ibm_runtime/utils/utils.py @@ -165,7 +165,7 @@ def _is_valid_rzz_pub_helper(circuit: QuantumCircuit) -> str | set[Parameter]: angle = instruction.operation.params[0] if isinstance(angle, ParameterExpression): angle_params.add(angle) - elif angle < 0.0 or angle > np.pi / 2 + 1e-10: + elif np.isnan(angle) or angle < 0.0 or angle > np.pi / 2 + 1e-10: return ( "The instruction rzz is supported only for angles in the " f"range [0, pi/2], but an angle ({angle}) outside of this " @@ -218,8 +218,8 @@ def is_valid_rzz_pub(pub: EstimatorPub | SamplerPub) -> str: projected_arr = arr[:, col_indices] for row in projected_arr: - angle = float(param_exp.bind(dict(zip(param_exp.parameters, row)))) - if angle < 0.0 or angle > np.pi / 2 + 1e-10: + angle = float(param_exp.bind_all(dict(zip(param_exp.parameters, row)))) + if np.isnan(angle) or angle < 0.0 or angle > np.pi / 2 + 1e-10: vals_msg = ", ".join( [f"{param_name}={param_val}" for param_name, param_val in zip(param_names, row)] ) diff --git a/release-notes/unreleased/2452.bug.rst b/release-notes/unreleased/2452.bug.rst new file mode 100644 index 0000000000..6bc82116bb --- /dev/null +++ b/release-notes/unreleased/2452.bug.rst @@ -0,0 +1,3 @@ +Reject fixed and parameterized ``RZZ`` angles that evaluate to ``NaN`` during +primitive input validation, instead of accepting the value or leaking a raw +Qiskit binding error. diff --git a/test/unit/test_sampler.py b/test/unit/test_sampler.py index 93dec5f68b..d7aca18266 100644 --- a/test/unit/test_sampler.py +++ b/test/unit/test_sampler.py @@ -299,7 +299,7 @@ def test_isa_inside_condition_block_body_in_separate_circuit(self, backend): with self.assertRaises(IBMInputValueError): SamplerV2(backend).run(pubs=[circ]) - @data(-1, 1, 2) + @data(-1, 1, 2, np.nan) def test_rzz_fixed_angle_validation(self, angle): """Test exception when rzz gate is used with an angle outside the range [0, pi/2].""" backend = FakeFractionalBackend() @@ -314,7 +314,7 @@ def test_rzz_fixed_angle_validation(self, angle): with self.assertRaisesRegex(IBMInputValueError, f"{angle}"): SamplerV2(backend).run(pubs=[circ]) - @data(-1, 1, 2) + @data(-1, 1, 2, np.nan) def test_rzz_parametrized_angle_validation(self, angle): """Test rzz gate with parameter outside range. @@ -334,6 +334,17 @@ def test_rzz_parametrized_angle_validation(self, angle): with self.assertRaisesRegex(IBMInputValueError, f"p={angle}"): SamplerV2(backend).run(pubs=[(circ, [angle])]) + def test_rzz_parametrized_angle_allows_eliminated_nan(self): + """Test that a NaN parameter is allowed when it is eliminated from the RZZ angle.""" + backend = FakeFractionalBackend() + param = Parameter("p") + + circ = QuantumCircuit(2) + circ.rzz(0 * param + 1, 0, 1) + circ.measure_all() + + SamplerV2(backend).run(pubs=[(circ, [np.nan])]) + @data([1.0, 2.0], [1.0, 0.0]) @unpack def test_rzz_validation_param_exp(self, val1, val2):