Skip to content

Commit

Permalink
Deprecate pulse parameter scoping (#11691)
Browse files Browse the repository at this point in the history
* Deprecate pulse reference parameter scoping

As noted in #11654, parameter
scoping does not work properly beginning with Qiskit 0.45.0. The problem
is that the `Parameter` name is now used for the hash value and so the
parameter produced with the scoped name is not equivalent to the
original parameter and does not substitute for it when using parameter
assignment. Since the scoped parameter mechanism was a convenience
feature and not widely used, it is simply deprecated rather than made to
work with parameter assignment.

* black

* Update releasenotes/notes/deprecate-pulse-parameter-scoping-6b6b50a394b57937.yaml

Co-authored-by: Jake Lishman <jake@binhbar.com>

* Move warning to documentation

---------

Co-authored-by: Jake Lishman <jake@binhbar.com>
Co-authored-by: Jake Lishman <jake.lishman@ibm.com>
  • Loading branch information
3 people authored Feb 1, 2024
1 parent 89d403d commit 6b914c7
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 32 deletions.
33 changes: 33 additions & 0 deletions qiskit/pulse/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from qiskit.pulse.instructions import Instruction, Reference
from qiskit.pulse.utils import instruction_duration_validation
from qiskit.pulse.reference_manager import ReferenceManager
from qiskit.utils.deprecation import deprecate_func
from qiskit.utils.multiprocessing import is_main_process


Expand Down Expand Up @@ -880,6 +881,12 @@ class ScheduleBlock:
.. rubric:: Program Scoping
.. note::
The :meth:`~ScheduleBlock.scoped_parameters` and
:meth:`~ScheduleBlock.search_parameters` methods described in this
section are deprecated.
When you call a subroutine from another subroutine, or append a schedule block
to another schedule block, the management of references and parameters
can be a hard task. Schedule block offers a convenient feature to help with this
Expand Down Expand Up @@ -1225,9 +1232,22 @@ def parameters(self) -> Set[Parameter]:

return out_params

@deprecate_func(
additional_msg=(
"There is no alternative to this method. Parameters must be mapped "
"to references by checking the reference schedules directly."
),
since="0.46.0",
removal_timeline="in the Qiskit 1.0 release",
)
def scoped_parameters(self) -> Tuple[Parameter]:
"""Return unassigned parameters with scoped names.
.. warning::
Scoped parameters do not work correctly with Qiskit's data model for parameter
assignment. This implementation is consequently being removed in Qiskit 1.0.
.. note::
If a parameter is defined within a nested scope,
Expand Down Expand Up @@ -1623,9 +1643,22 @@ def get_parameters(self, parameter_name: str) -> List[Parameter]:
matched = [p for p in self.parameters if p.name == parameter_name]
return matched

@deprecate_func(
additional_msg=(
"There is no alternative to this method. Parameters must be mapped "
"to references by checking the reference schedules directly."
),
since="0.46.0",
removal_timeline="in the Qiskit 1.0 release",
)
def search_parameters(self, parameter_regex: str) -> List[Parameter]:
"""Search parameter with regular expression.
.. warning::
Scoped parameters do not work correctly with Qiskit's data model for parameter
assignment. This implementation is consequently being removed in Qiskit 1.0.
This method looks for the scope-aware parameters.
For example,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
deprecations:
- |
The :meth:`.ScheduleBlock.scoped_parameters` and
:meth:`.ScheduleBlock.search_parameters` methods have been deprecated.
These methods produce :class:`.Parameter` objects with names modified to
indicate pulse scoping. The original intention of the methods was that
these objects would still link to the original unscoped :class:`.Parameter`
objects. However, the modification of the name breaks the link so that
assigning using the scoped version does not work. See `#11654
<https://github.com/Qiskit/qiskit/issues/11654>`__ for more context.
81 changes: 49 additions & 32 deletions test/python/pulse/test_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,16 @@ def test_append_schedule_parameter_scope(self):
with pulse.build() as sched_z1:
builder.append_schedule(sched_y1)

sched_param = next(iter(sched_z1.scoped_parameters()))
with self.assertWarns(DeprecationWarning):
sched_param = next(iter(sched_z1.scoped_parameters()))
self.assertEqual(sched_param.name, "root::name")

# object equality
self.assertEqual(
sched_z1.search_parameters("root::name")[0],
param,
)
with self.assertWarns(DeprecationWarning):
self.assertEqual(
sched_z1.search_parameters("root::name")[0],
param,
)

def test_refer_schedule(self):
"""Test refer to schedule by name.
Expand Down Expand Up @@ -108,20 +110,23 @@ def test_refer_schedule_parameter_scope(self):
sched_y1.assign_references({("x1", "d0"): sched_x1})
sched_z1.assign_references({("y1", "d0"): sched_y1})

sched_param = next(iter(sched_z1.scoped_parameters()))
with self.assertWarns(DeprecationWarning):
sched_param = next(iter(sched_z1.scoped_parameters()))
self.assertEqual(sched_param.name, "root::y1,d0::x1,d0::name")

# object equality
self.assertEqual(
sched_z1.search_parameters("root::y1,d0::x1,d0::name")[0],
param,
)
with self.assertWarns(DeprecationWarning):
self.assertEqual(
sched_z1.search_parameters("root::y1,d0::x1,d0::name")[0],
param,
)

# regex
self.assertEqual(
sched_z1.search_parameters(r"\S::x1,d0::name")[0],
param,
)
with self.assertWarns(DeprecationWarning):
self.assertEqual(
sched_z1.search_parameters(r"\S::x1,d0::name")[0],
param,
)

def test_call_schedule(self):
"""Test call schedule.
Expand Down Expand Up @@ -160,20 +165,23 @@ def test_call_schedule_parameter_scope(self):
with pulse.build() as sched_z1:
builder.call(sched_y1, name="y1")

sched_param = next(iter(sched_z1.scoped_parameters()))
with self.assertWarns(DeprecationWarning):
sched_param = next(iter(sched_z1.scoped_parameters()))
self.assertEqual(sched_param.name, "root::y1::x1::name")

# object equality
self.assertEqual(
sched_z1.search_parameters("root::y1::x1::name")[0],
param,
)
with self.assertWarns(DeprecationWarning):
self.assertEqual(
sched_z1.search_parameters("root::y1::x1::name")[0],
param,
)

# regex
self.assertEqual(
sched_z1.search_parameters(r"\S::x1::name")[0],
param,
)
with self.assertWarns(DeprecationWarning):
self.assertEqual(
sched_z1.search_parameters(r"\S::x1::name")[0],
param,
)

def test_append_and_call_schedule(self):
"""Test call and append schedule.
Expand Down Expand Up @@ -368,7 +376,8 @@ def test_special_parameter_name(self):
pulse.reference("sub", "q0")
sched_y1.assign_references({("sub", "q0"): sched_x1})

ret_param = sched_y1.search_parameters(r"\Ssub,q0::my.parameter_object")[0]
with self.assertWarns(DeprecationWarning):
ret_param = sched_y1.search_parameters(r"\Ssub,q0::my.parameter_object")[0]

self.assertEqual(param, ret_param)

Expand All @@ -392,10 +401,13 @@ def test_parameter_in_multiple_scope(self):
pulse.call(sched_y1, name="y1")

self.assertEqual(len(sched_z1.parameters), 1)
self.assertEqual(len(sched_z1.scoped_parameters()), 2)
with self.assertWarns(DeprecationWarning):
self.assertEqual(len(sched_z1.scoped_parameters()), 2)

self.assertEqual(sched_z1.search_parameters("root::x1::name")[0], param)
self.assertEqual(sched_z1.search_parameters("root::y1::name")[0], param)
with self.assertWarns(DeprecationWarning):
self.assertEqual(sched_z1.search_parameters("root::x1::name")[0], param)
with self.assertWarns(DeprecationWarning):
self.assertEqual(sched_z1.search_parameters("root::y1::name")[0], param)

def test_parallel_alignment_equality(self):
"""Testcase for potential edge case.
Expand Down Expand Up @@ -558,7 +570,8 @@ def test_lazy_ecr(self):
self.assertSetEqual(params, {"cr"})

# Parameter names are scoepd
scoped_params = {p.name for p in sched.scoped_parameters()}
with self.assertWarns(DeprecationWarning):
scoped_params = {p.name for p in sched.scoped_parameters()}
self.assertSetEqual(scoped_params, {"root::cr"})

# Assign CR and XP schedule to the empty reference
Expand All @@ -571,7 +584,8 @@ def test_lazy_ecr(self):
self.assertEqual(assigned_refs[("xp", "q0")], self.xp_sched)

# Parameter added from subroutines
scoped_params = {p.name for p in sched.scoped_parameters()}
with self.assertWarns(DeprecationWarning):
scoped_params = {p.name for p in sched.scoped_parameters()}
ref_params = {
# This is the cr parameter that belongs to phase_offset instruction in the root scope
"root::cr",
Expand All @@ -594,7 +608,8 @@ def test_lazy_ecr(self):
self.assertEqual(len(params), 2)

# Get parameter with scope, only xp amp
params = sched.search_parameters(parameter_regex="root::xp,q0::amp")
with self.assertWarns(DeprecationWarning):
params = sched.search_parameters(parameter_regex="root::xp,q0::amp")
self.assertEqual(len(params), 1)

def test_cnot(self):
Expand All @@ -614,11 +629,13 @@ def test_cnot(self):
pulse.call(ecr_sched, name="ecr")

# get parameter with scope, full scope is not needed
xp_amp = cx_sched.search_parameters(r"\S:xp::amp")[0]
with self.assertWarns(DeprecationWarning):
xp_amp = cx_sched.search_parameters(r"\S:xp::amp")[0]
self.assertEqual(self.xp_amp, xp_amp)

# get parameter with scope, of course full scope can be specified
xp_amp_full_scoped = cx_sched.search_parameters("root::ecr::xp::amp")[0]
with self.assertWarns(DeprecationWarning):
xp_amp_full_scoped = cx_sched.search_parameters("root::ecr::xp::amp")[0]
self.assertEqual(xp_amp_full_scoped, xp_amp)

# assign parameters
Expand Down

0 comments on commit 6b914c7

Please sign in to comment.