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

Assignment of parameters in pulse Schedule/ScheduleBlock doable through parameter name #12088

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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:
wshanks marked this conversation as resolved.
Show resolved Hide resolved
- |
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
Loading