Skip to content

Commit

Permalink
Extended rzz validation to include parameter expressions (#2093)
Browse files Browse the repository at this point in the history
* Validate parameter experessions in rzz gates

* fixed tests

* moved something to outside of a loop

* black

* lint

* a release note
  • Loading branch information
yaelbh authored Jan 21, 2025
1 parent cadf41d commit e703a6f
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 45 deletions.
47 changes: 24 additions & 23 deletions qiskit_ibm_runtime/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,9 @@ def _is_valid_rzz_pub_helper(circuit: QuantumCircuit) -> Union[str, Set[Paramete
# accurate).
if operation.name == "rzz":
angle = instruction.operation.params[0]
if isinstance(angle, Parameter):
angle_params.add(angle.name)
elif not isinstance(angle, ParameterExpression) and (
angle < 0.0 or angle > np.pi / 2 + 1e-10
):
if isinstance(angle, ParameterExpression):
angle_params.add(angle)
elif 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 of {angle} has been provided."
Expand Down Expand Up @@ -189,33 +187,36 @@ def is_valid_rzz_pub(pub: Union[EstimatorPub, SamplerPub]) -> str:
if len(helper_result) == 0:
return ""

# helper_result is a set of parameter names
# helper_result is a set of parameter expressions
rzz_params = list(helper_result)

# gather all parameter names, in order
pub_params = list(chain.from_iterable(pub.parameter_values.data))

col_indices = np.where(np.isin(pub_params, rzz_params))[0]
# col_indices is the indices of columns in the parameter value array that have to be checked
pub_params = np.array(list(chain.from_iterable(pub.parameter_values.data)))

# first axis will be over flattened shape, second axis over circuit parameters
arr = pub.parameter_values.ravel().as_array()

# project only to the parameters that have to be checked
arr = arr[:, col_indices]
for param_exp in rzz_params:
param_names = [param.name for param in param_exp.parameters]

# We allow an angle value of a bit more than pi/2, to compensate floating point rounding
# errors (beyond pi/2 does not trigger an error down the stack, only may become less
# accurate).
bad = np.where((arr < 0.0) | (arr > np.pi / 2 + 1e-10))
col_indices = [np.where(pub_params == param_name)[0][0] for param_name in param_names]
# col_indices is the indices of columns in the parameter value array that have to be checked

# `bad` is a tuple of two arrays, which can be empty, like this:
# (array([], dtype=int64), array([], dtype=int64))
if len(bad[0]) > 0:
return (
f"Assignment of value {arr[bad[0][0], bad[1][0]]} to Parameter "
f"'{pub_params[col_indices[bad[1][0]]]}' is an invalid angle for the rzz gate"
)
# project only to the parameters that have to be checked
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:
vals_msg = ", ".join(
[f"{param_name}={param_val}" for param_name, param_val in zip(param_names, row)]
)
return (
"The instruction rzz is supported only for angles in the "
f"range [0, pi/2], but an angle of {angle} has been provided; "
f"via parameter value(s) {vals_msg}, substituted in parameter expression "
f"{param_exp}."
)

return ""

Expand Down
1 change: 1 addition & 0 deletions release-notes/unreleased/2093.other.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Parameter expressions with RZZ gates will be checked against the values assigned to them in the pub. An `IBMInputValueError` will be raised if parameter values specified in the pub make a parameter expression evaluate to an invalid angle (negative or greater than `pi/2`).
44 changes: 22 additions & 22 deletions test/unit/test_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from unittest.mock import MagicMock

from ddt import data, ddt, named_data
from ddt import data, ddt, named_data, unpack
from packaging.version import Version, parse as parse_version
import numpy as np

Expand Down Expand Up @@ -312,10 +312,28 @@ def test_rzz_parametrized_angle_validation(self, angle):
if angle == 1:
SamplerV2(backend).run(pubs=[(circ, [angle])])
else:
with self.assertRaisesRegex(IBMInputValueError, f"{angle}.*Parameter 'p'"):
with self.assertRaisesRegex(IBMInputValueError, f"p={angle}"):
SamplerV2(backend).run(pubs=[(circ, [angle])])

@data(("a", -1), ("b", 2), ("d", 3), (-1, 1), (1, 2), None)
@data([1.0, 2.0], [1.0, 0.0])
@unpack
def test_rzz_validation_param_exp(self, val1, val2):
"""Test exception when rzz gate is used with a parameter expression, which is evaluated to
a value outside the range [0, pi/2]"""
backend = FakeFractionalBackend()
p1 = Parameter("p1")
p2 = Parameter("p2")

circ = QuantumCircuit(2)
circ.rzz(2 * p2 + p1, 0, 1)

if val2 == 0:
SamplerV2(backend).run(pubs=[(circ, [val1, val2])])
else:
with self.assertRaisesRegex(IBMInputValueError, f"p2={val2}, p1={val1}"):
SamplerV2(backend).run(pubs=[(circ, [val1, val2])])

@data(("a", -1.0), ("b", 2.0), ("d", 3.0), (-1.0, 1.0), (1.0, 2.0), None)
def test_rzz_complex(self, flawed_params):
"""Testing rzz validation, a variation of test_rzz_parametrized_angle_validation which
tests a more complex case. In addition, we test the currently non-existing case of dynamic
Expand Down Expand Up @@ -355,13 +373,10 @@ def test_rzz_complex(self, flawed_params):
if flawed_params is not None and isinstance(flawed_params[0], str):
if flawed_params[0] == "a":
val_ab[0, 1, 1, 0] = flawed_params[1]
val_ab[1, 0, 2, 1] = flawed_params[1]
if flawed_params[0] == "b":
val_ab[1, 0, 2, 1] = flawed_params[1]
val_d[1, 1, 1] = flawed_params[1]
if flawed_params[0] == "d":
val_d[1, 1, 1] = flawed_params[1]
val_ab[1, 1, 2, 1] = flawed_params[1]

pub = (circ, {("a", "b"): val_ab, "c": val_c, "d": val_d})

Expand All @@ -370,7 +385,7 @@ def test_rzz_complex(self, flawed_params):
else:
if isinstance(flawed_params[0], str):
with self.assertRaisesRegex(
IBMInputValueError, f"{flawed_params[1]}.*Parameter '{flawed_params[0]}'"
IBMInputValueError, f"{flawed_params[0]}={flawed_params[1]}"
):
SamplerV2(backend).run(pubs=[pub])
else:
Expand All @@ -379,21 +394,6 @@ def test_rzz_complex(self, flawed_params):
):
SamplerV2(backend).run(pubs=[pub])

def test_rzz_validation_skips_param_exp(self):
"""Verify that the rzz validation occurs only when the angle is a number or a parameter,
but not a parameter expression"""
backend = FakeFractionalBackend()
param = Parameter("p")

circ = QuantumCircuit(2)
circ.rzz(2 * param, 0, 1)

# Since we currently don't validate parameter expressions, the following line should run
# without an error, in spite of the angle being larger than pi/2
# (if there is an error, it is an expected one, such as a parameter expression being
# treated as if it were a float)
SamplerV2(backend).run(pubs=[(circ, [1])])

def test_param_expressions_gen3_runtime(self):
"""Verify that parameter expressions are not used in combination with the gen3-turbo
execution path."""
Expand Down

0 comments on commit e703a6f

Please sign in to comment.