Skip to content

Commit

Permalink
ParameterVector support for pulse parameter assignment (#12045)
Browse files Browse the repository at this point in the history
* Added test and release notes update

* Reformatting

* Added test and release notes update

* Reformatting

* Added compatibility of pulse ParameterManager with ParameterVector

It is now possible to assign to a pulse schedule parameters in the form of a list of values that can be directly binded to ParameterVector. This PR is based on the current functioning of the analogous method for the QuantumCircuit class.

* Commit requested changes

Type for submitting a list of parameters has been set to Sequence for the case of ParameterVector. This enables the user to also pass a tuple of values/ParameterExpressions

* Update pulse_parameter_manager_compat_with_ParameterVector-7d31395fd4019827.yaml

Co-authored-by: TsafrirA <113579969+TsafrirA@users.noreply.github.com>

* Added tests for Schedule and case of mix Parameter+numeric values

Complementary tests have been added for checking the pulse.Schedule.assign_parameters() method, as well as the functioning of binding to a ParameterVector a collection of numeric values and new ParameterExpression (through Parameter).

* Corrected error for schedule test

An error was occurring when trying to get access to an instruction parameter within schedule.instructions

* Correction to test schedule

* Added test and release notes update

* Reformatting

* Added test and release notes update

* Reformatting

* Added compatibility of pulse ParameterManager with ParameterVector

It is now possible to assign to a pulse schedule parameters in the form of a list of values that can be directly binded to ParameterVector. This PR is based on the current functioning of the analogous method for the QuantumCircuit class.

* Commit requested changes

Type for submitting a list of parameters has been set to Sequence for the case of ParameterVector. This enables the user to also pass a tuple of values/ParameterExpressions

* Update pulse_parameter_manager_compat_with_ParameterVector-7d31395fd4019827.yaml

Co-authored-by: TsafrirA <113579969+TsafrirA@users.noreply.github.com>

* Added tests for Schedule and case of mix Parameter+numeric values

Complementary tests have been added for checking the pulse.Schedule.assign_parameters() method, as well as the functioning of binding to a ParameterVector a collection of numeric values and new ParameterExpression (through Parameter).

* Corrected error for schedule test

An error was occurring when trying to get access to an instruction parameter within schedule.instructions

* Correction to test schedule

* Update releasenotes/notes/pulse_parameter_manager_compat_with_ParameterVector-7d31395fd4019827.yaml

Co-authored-by: Will Shanks <wshaos@posteo.net>

---------

Co-authored-by: TsafrirA <113579969+TsafrirA@users.noreply.github.com>
Co-authored-by: Will Shanks <wshaos@posteo.net>
  • Loading branch information
3 people committed Mar 24, 2024
1 parent b0d2062 commit 88b5193
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 11 deletions.
47 changes: 44 additions & 3 deletions qiskit/pulse/parameter_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@
"""
from __future__ import annotations
from copy import copy
from typing import Any
from typing import Any, Mapping, Sequence

from qiskit.circuit import ParameterVector
from qiskit.circuit.parameter import Parameter
from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType
from qiskit.pulse import instructions, channels
Expand Down Expand Up @@ -360,7 +361,9 @@ def get_parameters(self, parameter_name: str) -> list[Parameter]:
def assign_parameters(
self,
pulse_program: Any,
value_dict: dict[ParameterExpression, ParameterValueType],
value_dict: dict[
ParameterExpression | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
],
) -> Any:
"""Modify and return program data with parameters assigned according to the input.
Expand All @@ -372,7 +375,10 @@ def assign_parameters(
Returns:
Updated program data.
"""
valid_map = {k: value_dict[k] for k in value_dict.keys() & self._parameters}
unrolled_value_dict = self._unroll_param_dict(value_dict)
valid_map = {
k: unrolled_value_dict[k] for k in unrolled_value_dict.keys() & self._parameters
}
if valid_map:
visitor = ParameterSetter(param_map=valid_map)
return visitor.visit(pulse_program)
Expand All @@ -387,3 +393,38 @@ def update_parameter_table(self, new_node: Any):
visitor = ParameterGetter()
visitor.visit(new_node)
self._parameters |= visitor.parameters

def _unroll_param_dict(
self,
parameter_binds: Mapping[
Parameter | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
],
) -> Mapping[Parameter, ParameterValueType]:
"""
Unroll parameter dictionary to a map from parameter to value.
Args:
parameter_binds: A dictionary from parameter to value or a list of values.
Returns:
A dictionary from parameter to value.
"""
out = {}
for parameter, value in parameter_binds.items():
if isinstance(parameter, ParameterVector):
if not isinstance(value, Sequence):
raise PulseError(
f"Parameter vector '{parameter.name}' has length {len(parameter)},"
f" but was assigned to a single value."
)
if len(parameter) != len(value):
raise PulseError(
f"Parameter vector '{parameter.name}' has length {len(parameter)},"
f" but was assigned to {len(value)} values."
)
out.update(zip(parameter, value))
elif isinstance(parameter, str):
out[self.get_parameters(parameter)] = value
else:
out[parameter] = value
return out
23 changes: 16 additions & 7 deletions qiskit/pulse/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@
import sys
import warnings
from collections.abc import Callable, Iterable
from typing import List, Tuple, Union, Dict, Any
from typing import List, Tuple, Union, Dict, Any, Sequence

import numpy as np
import rustworkx as rx

from qiskit.circuit import ParameterVector
from qiskit.circuit.parameter import Parameter
from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType
from qiskit.pulse.channels import Channel
Expand Down Expand Up @@ -711,13 +712,18 @@ def is_parameterized(self) -> bool:
return self._parameter_manager.is_parameterized()

def assign_parameters(
self, value_dict: dict[ParameterExpression, ParameterValueType], inplace: bool = True
self,
value_dict: dict[
ParameterExpression | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
],
inplace: bool = True,
) -> "Schedule":
"""Assign the parameters in this schedule according to the input.
Args:
value_dict: A mapping from Parameters to either numeric values or another
Parameter expression.
value_dict: A mapping from parameters (parameter vectors) to either
numeric values (list of numeric values)
or another Parameter expression (list of Parameter expressions).
inplace: Set ``True`` to override this instance with new parameter.
Returns:
Expand Down Expand Up @@ -1408,14 +1414,17 @@ def is_referenced(self) -> bool:

def assign_parameters(
self,
value_dict: dict[ParameterExpression, ParameterValueType],
value_dict: dict[
ParameterExpression | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
],
inplace: bool = True,
) -> "ScheduleBlock":
"""Assign the parameters in this schedule according to the input.
Args:
value_dict: A mapping from Parameters to either numeric values or another
Parameter expression.
value_dict: A mapping from parameters (parameter vectors) to either numeric values
(list of numeric values)
or another parameter expression (list of parameter expressions).
inplace: Set ``True`` to override this instance with new parameter.
Returns:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
features_pulse:
- |
The ``assign_parameters`` methods of :class:`.Schedule` and :class:`.ScheduleBlock`
now support assigning a :class:`.ParameterVector` to a list of parameter values
simultaneously in addition to assigning individual :class:`.Parameter` instances to
individual values.
34 changes: 33 additions & 1 deletion test/python/pulse/test_parameter_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import numpy as np

from qiskit import pulse
from qiskit.circuit import Parameter
from qiskit.circuit import Parameter, ParameterVector
from qiskit.pulse.exceptions import PulseError, UnassignedDurationError
from qiskit.pulse.parameter_manager import ParameterGetter, ParameterSetter
from qiskit.pulse.transforms import AlignEquispaced, AlignLeft, inline_subroutines
Expand Down Expand Up @@ -483,6 +483,38 @@ def test_parametric_pulses(self):
self.assertEqual(block.blocks[0].pulse.amp, 0.2)
self.assertEqual(block.blocks[0].pulse.sigma, 4.0)

def test_parametric_pulses_with_parameter_vector(self):
"""Test Parametric Pulses with parameters determined by a ParameterVector
in the Play instruction."""
param_vec = ParameterVector("param_vec", 3)
param = Parameter("param")

waveform = pulse.library.Gaussian(duration=128, sigma=param_vec[0], amp=param_vec[1])

block = pulse.ScheduleBlock()
block += pulse.Play(waveform, pulse.DriveChannel(10))
block += pulse.ShiftPhase(param_vec[2], pulse.DriveChannel(10))
block1 = block.assign_parameters({param_vec: [4, 0.2, 0.1]}, inplace=False)
block2 = block.assign_parameters({param_vec: [4, param, 0.1]}, inplace=False)
self.assertEqual(block1.blocks[0].pulse.amp, 0.2)
self.assertEqual(block1.blocks[0].pulse.sigma, 4.0)
self.assertEqual(block1.blocks[1].phase, 0.1)
self.assertEqual(block2.blocks[0].pulse.amp, param)
self.assertEqual(block2.blocks[0].pulse.sigma, 4.0)
self.assertEqual(block2.blocks[1].phase, 0.1)

sched = pulse.Schedule()
sched += pulse.Play(waveform, pulse.DriveChannel(10))
sched += pulse.ShiftPhase(param_vec[2], pulse.DriveChannel(10))
sched1 = sched.assign_parameters({param_vec: [4, 0.2, 0.1]}, inplace=False)
sched2 = sched.assign_parameters({param_vec: [4, param, 0.1]}, inplace=False)
self.assertEqual(sched1.instructions[0][1].pulse.amp, 0.2)
self.assertEqual(sched1.instructions[0][1].pulse.sigma, 4.0)
self.assertEqual(sched1.instructions[1][1].phase, 0.1)
self.assertEqual(sched2.instructions[0][1].pulse.amp, param)
self.assertEqual(sched2.instructions[0][1].pulse.sigma, 4.0)
self.assertEqual(sched2.instructions[1][1].phase, 0.1)


class TestScheduleTimeslots(QiskitTestCase):
"""Test for edge cases of timing overlap on parametrized channels.
Expand Down

0 comments on commit 88b5193

Please sign in to comment.