Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify values of parametrized rzz gates #2021

Merged
merged 45 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a3f4dd2
Verify values of parametrized rzz gates
yaelbh Nov 7, 2024
fc58814
black
yaelbh Nov 7, 2024
b663a47
lint
yaelbh Nov 7, 2024
a345181
black
yaelbh Nov 7, 2024
481d8ea
lint
yaelbh Nov 7, 2024
00dd58b
lint
yaelbh Nov 7, 2024
7040adf
mypy
yaelbh Nov 7, 2024
a8fcb0d
mypy
yaelbh Nov 7, 2024
bc333e7
fix
yaelbh Nov 7, 2024
b4482ae
fix
yaelbh Nov 7, 2024
62ed9ad
fix
yaelbh Nov 7, 2024
1334572
bug fix
yaelbh Nov 10, 2024
457336c
updated the parametrized angle test
yaelbh Nov 10, 2024
c5c2007
fixes
yaelbh Nov 10, 2024
b625775
adding a test
yaelbh Nov 10, 2024
e3e5471
test_rzz_recursive
yaelbh Nov 10, 2024
8bfd207
use assertRaisesRegex to refine assertions
yaelbh Nov 10, 2024
8c5f307
black
yaelbh Nov 10, 2024
96595f1
lint
yaelbh Nov 10, 2024
28ee978
removed a redundant test
yaelbh Nov 11, 2024
394ac3c
Merge branch 'main' into pvalrzz
yaelbh Nov 11, 2024
3abfb17
skip parameter expression
yaelbh Nov 12, 2024
ca5fd34
black
yaelbh Nov 12, 2024
2bfd24d
check fixed angles also in is_isa_circuit
yaelbh Nov 12, 2024
505f5ee
black
yaelbh Nov 12, 2024
e4d6054
Merge branch 'main' into pvalrzz
yaelbh Nov 12, 2024
926f91c
removed extra white space in error message
yaelbh Nov 14, 2024
bb6a947
Update qiskit_ibm_runtime/utils/utils.py
yaelbh Nov 15, 2024
984da2e
Merge branch 'main' into pvalrzz
yaelbh Nov 18, 2024
7b05181
spell fix
yaelbh Nov 20, 2024
9383c71
Update qiskit_ibm_runtime/utils/utils.py
yaelbh Nov 21, 2024
a786372
Update qiskit_ibm_runtime/utils/utils.py
yaelbh Nov 21, 2024
660524e
release notes
yaelbh Nov 24, 2024
c0d4352
Merge branch 'pvalrzz' of github.com:yaelbh/qiskit-ibm-runtime into p…
yaelbh Nov 24, 2024
3172cdb
Merge branch 'main' into pvalrzz
yaelbh Nov 24, 2024
caf7d37
black
yaelbh Nov 24, 2024
099b990
changed tolerated rounding error
yaelbh Nov 24, 2024
9d5276f
lint
yaelbh Nov 24, 2024
a6ac62f
changed wording in the release note
yaelbh Nov 25, 2024
e005c94
Update release-notes/unreleased/2021.feat.rst
yaelbh Nov 26, 2024
e98bcc1
renamed functions
yaelbh Nov 26, 2024
bed8326
revised a comment
yaelbh Nov 26, 2024
14eec29
Merge branch 'main' into pvalrzz
wshanks Nov 26, 2024
b8f68ed
Merge branch 'main' into pvalrzz
kt474 Dec 2, 2024
8396c16
Merge branch 'main' into pvalrzz
yaelbh Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions qiskit_ibm_runtime/base_primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,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,
validate_rzz_pubs,
validate_no_param_expressions_gen3_runtime,
)
from .utils.default_session import get_cm_session
Expand Down Expand Up @@ -153,6 +155,8 @@ def _run(self, pubs: Union[list[EstimatorPub], list[SamplerPub]]) -> RuntimeJobV
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:
if not is_simulator(self._backend):
validate_rzz_pubs(pubs)
for pub in pubs:
if getattr(self._backend, "target", None) and not is_simulator(self._backend):
validate_isa_circuits([pub.circuit], self._backend.target)
Expand Down
1 change: 1 addition & 0 deletions qiskit_ibm_runtime/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
validate_no_dd_with_dynamic_circuits,
validate_isa_circuits,
validate_job_tags,
validate_rzz_pubs,
validate_no_param_expressions_gen3_runtime,
)

Expand Down
101 changes: 99 additions & 2 deletions qiskit_ibm_runtime/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
import re
from queue import Queue
from threading import Condition
from typing import List, Optional, Any, Dict, Union, Tuple
from typing import List, Optional, Any, Dict, Union, Tuple, Set
from urllib.parse import urlparse
from itertools import chain
import numpy as np

import requests
Expand All @@ -39,6 +40,9 @@
)
from qiskit.transpiler import Target
from qiskit.providers.backend import BackendV1, BackendV2
from qiskit.primitives.containers.estimator_pub import EstimatorPub
from qiskit.primitives.containers.sampler_pub import SamplerPub

from .deprecation import deprecate_function


Expand Down Expand Up @@ -82,7 +86,7 @@ def _is_isa_circuit_helper(circuit: QuantumCircuit, target: Target, qubit_map: D
if (
name == "rzz"
and not isinstance((param := instruction.operation.params[0]), ParameterExpression)
and (param < 0.0 or param > 1.001 * np.pi / 2)
and (param < 0.0 or param > np.pi / 2 + 1e-10)
):
return (
f"The instruction {name} on qubits {qargs} is supported only for angles in the "
Expand Down Expand Up @@ -123,6 +127,99 @@ def is_isa_circuit(circuit: QuantumCircuit, target: Target) -> str:
return _is_isa_circuit_helper(circuit, target, qubit_map)


def _is_valid_rzz_pub_helper(circuit: QuantumCircuit) -> Union[str, Set[Parameter]]:
"""
For rzz gates:
- Verify that numeric angles are in the range [0, pi/2]
- Collect parameterized angles

Returns one of the following:
- A string, containing an error message, if a numeric angle is outside of the range [0, pi/2]
- A list of names of all the parameters that participate in an rzz gate

Note: we check for parametrized rzz gates inside control flow operation, although fractional
gates are actually impossible in combination with dynamic circuits. This is in order to remain
correct if this restriction is removed at some point.
"""
angle_params = set()

for instruction in circuit.data:
operation = instruction.operation

# rzz gate is calibrated only for the range [0, pi/2].
# 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).
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
):
return (
"The instruction rzz is supported only for angles in the "
f"range [0, pi/2], but an angle of {angle} has been provided."
)

if isinstance(operation, ControlFlowOp):
for sub_circ in operation.blocks:
body_result = _is_valid_rzz_pub_helper(sub_circ)
if isinstance(body_result, str):
return body_result
angle_params.update(body_result)

return angle_params


def is_valid_rzz_pub(pub: Union[EstimatorPub, SamplerPub]) -> str:
"""Verify that all rzz angles are in the range [0, pi/2].

Args:
pub: A pub to be checked

Returns:
An empty string if all angles are valid, otherwise an error message.
"""
helper_result = _is_valid_rzz_pub_helper(pub.circuit)

if isinstance(helper_result, str):
return helper_result

if len(helper_result) == 0:
return ""

# helper_result is a set of parameter names
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

# 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]

# 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))

# `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"
)

return ""


def are_circuits_dynamic(circuits: List[QuantumCircuit], qasm_default: bool = True) -> bool:
"""Checks if the input circuits are dynamic."""
for circuit in circuits:
Expand Down
15 changes: 14 additions & 1 deletion qiskit_ibm_runtime/utils/validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# that they have been altered from the originals.

"""Utilities for data validation."""
from typing import List, Sequence, Optional, Any
from typing import List, Sequence, Optional, Any, Union
import warnings
import keyword

Expand All @@ -22,6 +22,7 @@
from qiskit_ibm_runtime.utils.utils import (
is_isa_circuit,
are_circuits_dynamic,
is_valid_rzz_pub,
has_param_expressions,
)
from qiskit_ibm_runtime.exceptions import IBMInputValueError
Expand Down Expand Up @@ -102,6 +103,18 @@ def validate_isa_circuits(circuits: Sequence[QuantumCircuit], target: Target) ->
)


def validate_rzz_pubs(pubs: Union[List[EstimatorPub], List[SamplerPub]]) -> None:
"""Validate that rzz angles are always in the range [0, pi/2]

Args:
pubs: A list of pubs.
"""
for pub in pubs:
message = is_valid_rzz_pub(pub)
if message:
raise IBMInputValueError(message)


def validate_no_dd_with_dynamic_circuits(circuits: List[QuantumCircuit], options: Any) -> None:
"""Validate that if dynamical decoupling options are enabled,
no circuit in the pubs is dynamic
Expand Down
1 change: 1 addition & 0 deletions release-notes/unreleased/2021.feat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A new function ``validate_rzz_pubs`` was added. The function verifies that ``rzz`` parameters are in the range between ``0`` and ``pi/2``, for numeric parameters (e.g. ``rzz(np.pi/4, 0)``), and for unbounded parameters (``rzz(theta, 0)``) with values to substitute provided in the pub. Parameter expressions (e.g. ``rzz(theta + phi, 0)``) are still not validated.
98 changes: 88 additions & 10 deletions test/unit/test_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def test_isa_inside_condition_block_body_in_separate_circuit(self, backend):
SamplerV2(backend).run(pubs=[(circ)])

@data(-1, 1, 2)
def test_rzz_angle_validation(self, angle):
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()

Expand All @@ -296,12 +296,13 @@ def test_rzz_angle_validation(self, angle):
if angle == 1:
SamplerV2(backend).run(pubs=[(circ)])
else:
with self.assertRaises(IBMInputValueError):
with self.assertRaisesRegex(IBMInputValueError, f"{angle}"):
SamplerV2(backend).run(pubs=[(circ)])

def test_rzz_validates_only_for_fixed_angles(self):
"""Verify that the rzz validation occurs only when the angle is a number, and not a
parameter"""
@data(-1, 1, 2)
def test_rzz_parametrized_angle_validation(self, angle):
"""Test exception when rzz gate is used with a parameter which is assigned a value outside
the range [0, pi/2]"""
backend = FakeFractionalBackend()
param = Parameter("p")

Expand All @@ -311,11 +312,88 @@ def test_rzz_validates_only_for_fixed_angles(self):
# Should run without an error
SamplerV2(backend).run(pubs=[(circ, [1])])

with self.subTest("parameter expression"):
circ = QuantumCircuit(2)
circ.rzz(2 * param, 0, 1)
# Should run without an error
SamplerV2(backend).run(pubs=[(circ, [0.5])])
if angle == 1:
SamplerV2(backend).run(pubs=[(circ, [angle])])
else:
with self.assertRaisesRegex(IBMInputValueError, f"{angle}.*Parameter 'p'"):
SamplerV2(backend).run(pubs=[(circ, [angle])])

@data(("a", -1), ("b", 2), ("d", 3), (-1, 1), (1, 2), None)
def test_rzz_complex(self, flawed_params):
"""Testing rzz validation in the currently non-existing case of dynamic instructions"""
# pylint: disable=not-context-manager

# FakeFractionalBackend has both fractional and dynamic instructions
backend = FakeFractionalBackend()

aparam = Parameter("a")
bparam = Parameter("b")
cparam = Parameter("c")
dparam = Parameter("d")

angle1 = 1
angle2 = 1
if flawed_params is not None and not isinstance(flawed_params[0], str):
angle1 = flawed_params[0]
angle2 = flawed_params[1]

circ = QuantumCircuit(2, 1)
circ.rzz(bparam, 0, 1)
circ.rzz(angle1, 0, 1)
circ.measure(0, 0)
with circ.if_test((0, 1)):
circ.rzz(aparam, 0, 1)
circ.rzz(angle2, 0, 1)
circ.rx(cparam, 0)
circ.rzz(dparam, 0, 1)
circ.rzz(1, 0, 1)
circ.rzz(aparam, 0, 1)

val_ab = np.ones([2, 2, 3, 2])
val_c = (-1) * np.ones([2, 2, 3])
val_d = np.ones([2, 2, 3])

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})

if flawed_params is None:
SamplerV2(backend).run(pubs=[pub])
else:
if isinstance(flawed_params[0], str):
with self.assertRaisesRegex(
IBMInputValueError, f"{flawed_params[1]}.*Parameter '{flawed_params[0]}'"
):
SamplerV2(backend).run(pubs=[pub])
else:
with self.assertRaisesRegex(
IBMInputValueError, f"{flawed_params[0] * flawed_params[1]}"
):
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
Expand Down
Loading