Skip to content

Commit

Permalink
Assignment of parameters in pulse Schedule/ScheduleBlock doable t…
Browse files Browse the repository at this point in the history
…hrough parameter name (#12088)

* Added possibility of assigning Parameter and ParameterVector by name

It is now possible to specify in the mapping the names of the parameters instead of the parameters themselves to assign parameters to pulse `Schedule`s and `ScheduleBlock`s. It is even possible to assign all the parameters of a ParameterVector by just specifying its name and setting as a value a list of parameter values.

* Update parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml

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

* Reshaped string parameter assignment

Corrected mistake in string assignment

Corrected mistake in string assignment

* Update releasenotes/notes/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml

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

* Enabled string assignment for multiple params carrying same name

The check is now based on the value type to infer if assignment should be done on Parameters or ParameterVectors

Removed unnecessary import from utils

Corrected string assignment

Correction part 2

Corrected test

The inplace=True argument was preventing the reuse of a parametrized waveform in the schedule, making the test fail

* Reformat

---------

Co-authored-by: Will Shanks <wshaos@posteo.net>
  • Loading branch information
arthurostrauss and wshanks authored Apr 29, 2024
1 parent 393524f commit b442b1c
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 24 deletions.
45 changes: 30 additions & 15 deletions qiskit/pulse/parameter_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,19 @@
from copy import copy
from typing import Any, Mapping, Sequence

from qiskit.circuit import ParameterVector
from qiskit.circuit.parametervector import ParameterVector, ParameterVectorElement
from qiskit.circuit.parameter import Parameter
from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType
from qiskit.pulse import instructions, channels
from qiskit.pulse.exceptions import PulseError
from qiskit.pulse.library import SymbolicPulse, Waveform
from qiskit.pulse.schedule import Schedule, ScheduleBlock
from qiskit.pulse.transforms.alignments import AlignmentKind
from qiskit.pulse.utils import format_parameter_value
from qiskit.pulse.utils import (
format_parameter_value,
_validate_parameter_vector,
_validate_parameter_value,
)


class NodeVisitor:
Expand Down Expand Up @@ -362,7 +366,8 @@ def assign_parameters(
self,
pulse_program: Any,
value_dict: dict[
ParameterExpression | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
ParameterExpression | ParameterVector | str,
ParameterValueType | Sequence[ParameterValueType],
],
) -> Any:
"""Modify and return program data with parameters assigned according to the input.
Expand Down Expand Up @@ -397,7 +402,7 @@ def update_parameter_table(self, new_node: Any):
def _unroll_param_dict(
self,
parameter_binds: Mapping[
Parameter | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
Parameter | ParameterVector | str, ParameterValueType | Sequence[ParameterValueType]
],
) -> Mapping[Parameter, ParameterValueType]:
"""
Expand All @@ -410,21 +415,31 @@ def _unroll_param_dict(
A dictionary from parameter to value.
"""
out = {}
param_name_dict = {param.name: [] for param in self.parameters}
for param in self.parameters:
param_name_dict[param.name].append(param)
param_vec_dict = {
param.vector.name: param.vector
for param in self.parameters
if isinstance(param, ParameterVectorElement)
}
for name in param_vec_dict.keys():
if name in param_name_dict:
param_name_dict[name].append(param_vec_dict[name])
else:
param_name_dict[name] = [param_vec_dict[name]]

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."
)
_validate_parameter_vector(parameter, value)
out.update(zip(parameter, value))
elif isinstance(parameter, str):
out[self.get_parameters(parameter)] = value
for param in param_name_dict[parameter]:
is_vec = _validate_parameter_value(param, value)
if is_vec:
out.update(zip(param, value))
else:
out[param] = value
else:
out[parameter] = value
return out
16 changes: 9 additions & 7 deletions qiskit/pulse/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,16 +715,17 @@ def is_parameterized(self) -> bool:
def assign_parameters(
self,
value_dict: dict[
ParameterExpression | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
ParameterExpression | ParameterVector | str,
ParameterValueType | Sequence[ParameterValueType],
],
inplace: bool = True,
) -> "Schedule":
"""Assign the parameters in this schedule according to the input.
Args:
value_dict: A mapping from parameters (parameter vectors) to either
numeric values (list of numeric values)
or another Parameter expression (list of Parameter expressions).
value_dict: A mapping from parameters or parameter names (parameter vector
or parameter vector name) 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 @@ -1416,15 +1417,16 @@ def is_referenced(self) -> bool:
def assign_parameters(
self,
value_dict: dict[
ParameterExpression | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
ParameterExpression | ParameterVector | str,
ParameterValueType | Sequence[ParameterValueType],
],
inplace: bool = True,
) -> "ScheduleBlock":
"""Assign the parameters in this schedule according to the input.
Args:
value_dict: A mapping from parameters (parameter vectors) to either numeric values
(list of numeric values)
value_dict: A mapping from parameters or parameter names (parameter vector
or parameter vector name) 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.
Expand Down
35 changes: 33 additions & 2 deletions qiskit/pulse/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
# that they have been altered from the originals.

"""Module for common pulse programming utilities."""
from typing import List, Dict, Union
from typing import List, Dict, Union, Sequence
import warnings

import numpy as np

from qiskit.circuit import ParameterVector, Parameter
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.pulse.exceptions import UnassignedDurationError, QiskitError
from qiskit.pulse.exceptions import UnassignedDurationError, QiskitError, PulseError


def format_meas_map(meas_map: List[List[int]]) -> Dict[int, List[int]]:
Expand Down Expand Up @@ -117,3 +118,33 @@ def instruction_duration_validation(duration: int):
raise QiskitError(
f"Instruction duration must be a non-negative integer, got {duration} instead."
)


def _validate_parameter_vector(parameter: ParameterVector, value):
"""Validate parameter vector and its value."""
if not isinstance(value, Sequence):
raise PulseError(
f"Parameter vector '{parameter.name}' has length {len(parameter)},"
f" but was assigned to {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."
)


def _validate_single_parameter(parameter: Parameter, value):
"""Validate single parameter and its value."""
if not isinstance(value, (int, float, complex, ParameterExpression)):
raise PulseError(f"Parameter '{parameter.name}' is not assignable to {value}.")


def _validate_parameter_value(parameter, value):
"""Validate parameter and its value."""
if isinstance(parameter, ParameterVector):
_validate_parameter_vector(parameter, value)
return True
else:
_validate_single_parameter(parameter, value)
return False
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
features_pulse:
- |
It is now possible to assign parameters to pulse :class:`.Schedule`and :class:`.ScheduleBlock` objects by specifying
the parameter name as a string. The parameter name can be used to assign values to all parameters within the
`Schedule` or `ScheduleBlock` that have the same name. Moreover, the parameter name of a `ParameterVector`
can be used to assign all values of the vector simultaneously (the list of values should therefore match the
length of the vector).
38 changes: 38 additions & 0 deletions test/python/pulse/test_parameter_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,44 @@ def test_parametric_pulses_with_parameter_vector(self):
self.assertEqual(sched2.instructions[0][1].pulse.sigma, 4.0)
self.assertEqual(sched2.instructions[1][1].phase, 0.1)

def test_pulse_assignment_with_parameter_names(self):
"""Test pulse assignment with parameter names."""
sigma = Parameter("sigma")
amp = Parameter("amp")
param_vec = ParameterVector("param_vec", 2)

waveform = pulse.library.Gaussian(duration=128, sigma=sigma, amp=amp)
waveform2 = pulse.library.Gaussian(duration=128, sigma=40, amp=amp)
block = pulse.ScheduleBlock()
block += pulse.Play(waveform, pulse.DriveChannel(10))
block += pulse.Play(waveform2, pulse.DriveChannel(10))
block += pulse.ShiftPhase(param_vec[0], pulse.DriveChannel(10))
block += pulse.ShiftPhase(param_vec[1], pulse.DriveChannel(10))
block1 = block.assign_parameters(
{"amp": 0.2, "sigma": 4, "param_vec": [3.14, 1.57]}, 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].pulse.amp, 0.2)
self.assertEqual(block1.blocks[2].phase, 3.14)
self.assertEqual(block1.blocks[3].phase, 1.57)

sched = pulse.Schedule()
sched += pulse.Play(waveform, pulse.DriveChannel(10))
sched += pulse.Play(waveform2, pulse.DriveChannel(10))
sched += pulse.ShiftPhase(param_vec[0], pulse.DriveChannel(10))
sched += pulse.ShiftPhase(param_vec[1], pulse.DriveChannel(10))
sched1 = sched.assign_parameters(
{"amp": 0.2, "sigma": 4, "param_vec": [3.14, 1.57]}, 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].pulse.amp, 0.2)
self.assertEqual(sched1.instructions[2][1].phase, 3.14)
self.assertEqual(sched1.instructions[3][1].phase, 1.57)


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

0 comments on commit b442b1c

Please sign in to comment.