From 23dbc67a2081d3d5b166b33a002b57c97513ecd0 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 5 Mar 2021 16:31:13 +0100 Subject: [PATCH 001/111] * First implementation of Frame. --- qiskit/pulse/frame.py | 121 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 qiskit/pulse/frame.py diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py new file mode 100644 index 000000000000..0fa715a3ae47 --- /dev/null +++ b/qiskit/pulse/frame.py @@ -0,0 +1,121 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Implements a Frame.""" + +from typing import Any, Set, Union +import numpy as np + +from qiskit.circuit import Parameter +from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType +from qiskit.pulse.exceptions import PulseError + + +class Frame: + """A frame is a frequency and a phase.""" + prefix = 'f' + + def __init__(self, index: Union[int, Parameter]): + """ + Args: + index: The index of the frame. + """ + self._validate_index(index) + self._index = index + self._hash = None + + self._parameters = set() + if isinstance(index, ParameterExpression): + self._parameters.update(index.parameters) + + @property + def index(self) -> int: + """Return the index of this frame. The index is a label for a frame.""" + return self._index + + @staticmethod + def _validate_index(index: Any) -> None: + """ + Raise a PulseError if the Frame index is invalid, namely, if it's not a positive + integer. + + Raises: + PulseError: If ``index`` is not a non-negative integer. + """ + if isinstance(index, ParameterExpression) and index.parameters: + # Parameters are unbound + return + elif isinstance(index, ParameterExpression): + index = float(index) + if index.is_integer(): + index = int(index) + + if not isinstance(index, (int, np.integer)) and index < 0: + raise PulseError('Frame index must be a nonnegative integer') + + @property + def name(self) -> str: + """Return the shorthand alias for this frame, which is based on its type and index.""" + return '{}{}'.format(self.__class__.prefix, self._index) + + @property + def parameters(self) -> Set: + """Parameters which determine the frame index.""" + return self._parameters + + def is_parameterized(self) -> bool: + """Return True iff the frame is parameterized.""" + return bool(self.parameters) + + def assign(self, parameter: Parameter, value: ParameterValueType) -> 'Frame': + """Return a new frame with the input Parameter assigned to value. + + Args: + parameter: A parameter in this expression whose value will be updated. + value: The new value to bind to. + + Returns: + A new frame with updated parameters. + + Raises: + PulseError: If the parameter is not present in the frame. + """ + if parameter not in self.parameters: + raise PulseError('Cannot bind parameters ({}) not present in the frame.' + ''.format(parameter)) + + new_index = self.index.assign(parameter, value) + if not new_index.parameters: + self._validate_index(new_index) + new_index = int(new_index) + + return type(self)(new_index) + + def __repr__(self): + return f'{self.__class__.__name__}({self._index})' + + def __eq__(self, other: 'Frame') -> bool: + """Return True iff self and other are equal, specifically, iff they have the same type + and the same index. + + Args: + other: The frame to compare to this frame. + + Returns: + True iff equal. + """ + return type(self) is type(other) and self.index == other.index + + def __hash__(self): + if self._hash is None: + self._hash = hash((type(self), self._index)) + return self._hash From 13cd3bec8731b7a752986750da94ccb9ff4dfc28 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 5 Mar 2021 16:43:50 +0100 Subject: [PATCH 002/111] * Added an implementation of Signal, i.e. a Pulse in a Frame. --- qiskit/pulse/library/signal.py | 100 +++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 qiskit/pulse/library/signal.py diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py new file mode 100644 index 000000000000..4f6cd8737121 --- /dev/null +++ b/qiskit/pulse/library/signal.py @@ -0,0 +1,100 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Implements a Signal.""" + +from typing import Any, Dict, Optional, Union + +from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType +from qiskit.pulse.library import Pulse +from qiskit.pulse.frame import Frame +from qiskit.pulse.exceptions import PulseError + + +class Signal: + """A Signal is a Pulse played in a given frame.""" + + def __init__(self, pulse: Pulse, frame: Frame, name: Optional[str] = None): + """ + Args: + pulse: The envelope of the signal. + frame: A reference to a frame onto which the envelope is multiplied. + name: Name of the signal. + + Raises: + PulseError: if the pulse is not a Pulse. + """ + if not isinstance(pulse, Pulse): + raise PulseError("The `pulse` argument to `Signal` must be of type `library.Pulse`.") + + self._pulse = pulse + self._frame = frame + self.name = name + + @property + def id(self) -> int: + """Unique identifier for this signal.""" + return id(self) + + @property + def pulse(self) -> Pulse: + """Return the envelope.""" + return self._pulse + + @property + def frame(self) -> Frame: + """Return the frame.""" + return self._frame + + @property + def duration(self) -> Union[int, ParameterExpression]: + """Return the duration of the signal as the duration of the envelope.""" + return self._pulse.duration + + def is_parameterized(self) -> bool: + """Determine if there are any parameters in the Signal.""" + return self._pulse.is_parameterized() or self._frame.is_parameterized() + + def parameters(self) -> Dict[str, Any]: + """Return a list of parameters in the Signal.""" + parameters = self._pulse.parameters + + if self._frame.is_parameterized(): + parameters['index'] = self._frame.index + + return parameters + + def assign_parameters(self, + value_dict: Dict[ParameterExpression, ParameterValueType]) -> 'Signal': + """ + Return a new Signal with parameters assigned. + + Args: + value_dict: A mapping from Parameters to either numeric values or another + Parameter expression. + + Returns: + New signal with updated parameters. + """ + pulse = self._pulse.assign_parameters(value_dict) + frame = self._frame + for param, value in value_dict.items(): + if param in self._frame.parameters: + frame = self._frame.assign(param, value) + + return type(self)(pulse, frame) + + def __eq__(self, other: 'Signal') -> bool: + return self._pulse == other._pulse and self.frame == other.frame + + def __repr__(self): + return f'Signal({self._pulse}, {self._frame})' From 8f9fe8c5229387aac3bc19fe804957913b92ae37 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 5 Mar 2021 16:49:07 +0100 Subject: [PATCH 003/111] * Added trackers to help resolve frames. --- qiskit/pulse/resolved_frame.py | 249 +++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 qiskit/pulse/resolved_frame.py diff --git a/qiskit/pulse/resolved_frame.py b/qiskit/pulse/resolved_frame.py new file mode 100644 index 000000000000..55f981355194 --- /dev/null +++ b/qiskit/pulse/resolved_frame.py @@ -0,0 +1,249 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Implements a Frame.""" + +from abc import ABC +from typing import Dict, List, Union + +from qiskit.circuit import Parameter +from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType +from qiskit.pulse.channels import Channel, PulseChannel +from qiskit.pulse.frame import Frame +from qiskit.pulse.schedule import Schedule +from qiskit.pulse.instructions.frequency import SetFrequency, ShiftFrequency +from qiskit.pulse.instructions.phase import SetPhase, ShiftPhase +from qiskit.pulse.exceptions import PulseError + + +class Tracker(ABC): + """ + Implements a class to keep track of the phase and frequency of a frame + or a pulse channel in a given schedule. + """ + + def __init__(self, index: Union[int, Parameter]): + """ + Args: + index: The index of the Tracker. Corresponds to the index of a + :class:`~qiskit.pulse.Frame` or a channel. + """ + self._index = index + self._frequencies = [] + self._phases = [] + self._instructions = {} + + self._parameters = set() + if isinstance(index, ParameterExpression): + self._parameters.update(index.parameters) + + @property + def index(self) -> int: + """Return the index of the tracker.""" + return self._index + + def frequency(self, time: int) -> float: + """ + Get the most recent frequency of the tracker before time. + + Args: + time: The maximum time for which to get the frequency. + + Returns: + frequency: The frequency of the frame up until time. + """ + frequency = self._frequencies[0][1] + for time_freq in self._frequencies: + if time_freq[0] <= time: + frequency = time_freq[1] + else: + break + + return frequency + + def phase(self, time: int) -> float: + """ + Get the most recent phase of the tracker. + + Args: + time: The maximum time for which to get the phase. + + Returns: + phase: The phase of the frame up until time. + """ + phase = self._phases[0][1] + for time_freq in self._phases: + if time_freq[0] <= time: + phase = time_freq[1] + else: + break + + return phase + + def set_frequency(self, time: int, frequency: float): + """Insert a new frequency in the time-ordered frequencies.""" + + if time > 0 and time in set([_[0] for _ in self._frequencies]): + if self.frequency(time) != frequency: + raise PulseError(f'Frequency already added at time {time}.') + + insert_idx = 0 + for idx, time_freq in enumerate(self._frequencies): + if time_freq[0] < time: + insert_idx = idx + + self._frequencies.insert(insert_idx + 1, (time, frequency)) + + def set_phase(self, time: int, phase: float): + """Insert a new phase in the time-ordered phases.""" + + if time > 0 and time in set([_[0] for _ in self._phases]): + if self.phase(time) != phase: + raise PulseError(f'Phase already added at time {time} for Frame({self.index}).') + + insert_idx = 0 + for idx, time_freq in enumerate(self._phases): + if time_freq[0] < time: + insert_idx = idx + + self._phases.insert(insert_idx + 1, (time, phase)) + + +class ResolvedFrame(Tracker): + """ + A class to help the assembler determine the frequency and phase of a + Frame at any given point in time. + """ + + def __init__(self, frame: Frame, frequency: float, phase: float, + channels: List[Channel]): + """ + Args: + frame: The frame to track. + frequency: The initial frequency of the frame. + phase: The initial phase of the frame. + channels: The list of channels on which the frame instructions apply. + """ + super().__init__(frame.index) + self._frequencies = [(0, frequency)] + self._phases = [(0, phase)] + self._channels = channels + + for ch in self._channels: + self._parameters.update(ch.parameters) + + @property + def channels(self) -> List[Channel]: + """Returns the channels that this frame ties together.""" + return self._channels + + def set_frame_instructions(self, schedule: Schedule): + """ + Add all matching frame instructions in this schedule to self. + + Args: + schedule: The schedule from which to extract frame operations. + """ + frame_instruction_types = [ShiftPhase, SetPhase, ShiftFrequency, SetFrequency] + frame_instructions = schedule.filter(instruction_types=frame_instruction_types) + + for time, inst in frame_instructions.instructions: + if Frame(self._index) == inst.operands[1]: + if isinstance(inst, ShiftFrequency): + self.set_frequency(time, self.frequency(time) + inst.frequency) + elif isinstance(inst, SetFrequency): + self.set_frequency(time, inst.frequency) + elif isinstance(inst, ShiftPhase): + self.set_phase(time, self.phase(time) + inst.phase) + elif isinstance(inst, SetPhase): + self.set_phase(time, inst.phase) + else: + raise PulseError('Unexpected frame operation.') + + def assign(self, parameter: Parameter, value: ParameterValueType) -> 'ResolvedFrame': + """ + Override the base class's assign method to handle any links between the + parameter of the frame and the parameters of the sub-channels. + + Args: + parameter: The parameter to assign + value: The value of the parameter. + """ + return self.assign_parameters({parameter: value}) + + def assign_parameters(self, + value_dict: Dict[ParameterExpression, ParameterValueType] + ) -> 'ResolvedFrame': + """ + Assign the value of the parameters. + + Args: + value_dict: A mapping from Parameters to either numeric values or another + Parameter expression. + """ + assigned_sub_channels = self._assign_sub_channels(value_dict) + + new_index = None + if isinstance(self._index, ParameterExpression): + for param, value in value_dict.items(): + if param in self._index.parameters: + new_index = self._index.assign(param, value) + if not new_index.parameters: + new_index = int(new_index) + + if new_index is not None: + return type(self)(new_index, self._frequencies, self._phases, assigned_sub_channels) + + return type(self)(self._index, self._frequencies, self._phases, assigned_sub_channels) + + def _assign_sub_channels(self, value_dict: Dict[ParameterExpression, + ParameterValueType]) -> List['Channel']: + """ + Args: + value_dict: The keys are the parameters to assign and the values are the + values of the parameters. + + Returns: + Frame: A Frame in which the parameter has been assigned. + """ + sub_channels = [] + for ch in self._channels: + if isinstance(ch.index, ParameterExpression): + for param, value in value_dict.items(): + if param in ch.parameters: + ch = ch.assign(param, value) + + sub_channels.append(ch) + + return sub_channels + + def __repr__(self): + sub_str = '[' + ', '.join([ch.__repr__() for ch in self._channels]) + ']' + return f'{self.__class__.__name__}({self._index}, {sub_str})' + + +class ChannelTracker(Tracker): + """Class to track the phase and frequency of channels when resolving frames.""" + + def __init__(self, channel: PulseChannel): + """ + Args: + channel: The channel that this tracker tracks. + """ + super().__init__(channel.index) + self._channel = channel + self._frequencies = [] + self._phases = [] + + def is_initialized(self) -> bool: + """Return true if the channel has been initialized.""" + return len(self._frequencies) > 0 From 306ac10e4fffa66f2889d456e5c4d5b3dbdc1bb8 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 5 Mar 2021 16:50:15 +0100 Subject: [PATCH 004/111] * Added __init__ files. --- qiskit/pulse/__init__.py | 2 ++ qiskit/pulse/library/__init__.py | 1 + 2 files changed, 3 insertions(+) diff --git a/qiskit/pulse/__init__.py b/qiskit/pulse/__init__.py index 5cf795e7a451..2f63c7df0afa 100644 --- a/qiskit/pulse/__init__.py +++ b/qiskit/pulse/__init__.py @@ -469,3 +469,5 @@ ) from qiskit.pulse.library.samplers.decorators import functional_pulse from qiskit.pulse.schedule import Schedule +from qiskit.pulse.library.signal import Signal +from qiskit.pulse.frame import Frame diff --git a/qiskit/pulse/library/__init__.py b/qiskit/pulse/library/__init__.py index 21b3944dff79..c8badc9177ec 100644 --- a/qiskit/pulse/library/__init__.py +++ b/qiskit/pulse/library/__init__.py @@ -38,3 +38,4 @@ Drag, Constant) from .pulse import Pulse from .waveform import Waveform +from .signal import Signal From 7990fdd52f7ee614c16a1ceafc1f3c0c4e3287d1 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 5 Mar 2021 16:51:24 +0100 Subject: [PATCH 005/111] * Added ignore_frames argument to the right and left alignment contexts in the builder. --- qiskit/pulse/builder.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index 89704b98274e..722bfb4ec555 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -894,7 +894,7 @@ def wrapped_transform(*args, **kwargs): @_transform_context(transforms.align_left) -def align_left() -> ContextManager[None]: +def align_left(ignore_frames: bool = False) -> ContextManager[None]: """Left alignment pulse scheduling context. Pulse instructions within this context are scheduled as early as possible @@ -917,11 +917,16 @@ def align_left() -> ContextManager[None]: pulse.play(pulse.Constant(20, 1.0), d1) assert pulse_prog.ch_start_time(d0) == pulse_prog.ch_start_time(d1) + + Args: + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. """ @_transform_context(transforms.align_right) -def align_right() -> ContextManager[None]: +def align_right(ignore_frames: bool = False) -> ContextManager[None]: """Right alignment pulse scheduling context. Pulse instructions within this context are scheduled as late as possible @@ -944,6 +949,11 @@ def align_right() -> ContextManager[None]: pulse.play(pulse.Constant(20, 1.0), d1) assert pulse_prog.ch_stop_time(d0) == pulse_prog.ch_stop_time(d1) + + Args: + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. """ @@ -1456,7 +1466,7 @@ def play(pulse: Union[library.Pulse, np.ndarray], pulse: Pulse to play. channel: Channel to play pulse on. """ - if not isinstance(pulse, library.Pulse): + if not isinstance(pulse, (library.Pulse, library.Signal)): pulse = library.Waveform(pulse) append_instruction(instructions.Play(pulse, channel)) From 280669aec26070e37d3be452b37096e638a875b9 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 5 Mar 2021 16:53:45 +0100 Subject: [PATCH 006/111] * Added frames configuration to the assembler. --- qiskit/compiler/assembler.py | 61 ++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index 4f074ab64c55..9c0c79419ec4 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -29,6 +29,7 @@ from qiskit.providers.backend import Backend from qiskit.pulse.channels import PulseChannel from qiskit.pulse import Schedule +from qiskit.pulse.frame import Frame logger = logging.getLogger(__name__) @@ -61,6 +62,7 @@ def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule, parameter_binds: Optional[List[Dict[Parameter, float]]] = None, parametric_pulses: Optional[List[str]] = None, init_qubits: bool = True, + frames_config: Dict[int, Dict] = None, **run_config: Dict) -> Qobj: """Assemble a list of circuits or pulse schedules into a ``Qobj``. @@ -122,6 +124,11 @@ def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule, ['gaussian', 'constant'] init_qubits: Whether to reset the qubits to the ground state for each shot. Default: ``True``. + frames_config: Dictionary of user provided frames configuration. The key is the index + of the frame and the value is a dictionary with the configuration of the frame + which must be of the form {'frame': Frame, 'phase': float, 'frequency': float, + 'channels': List[Channels]}. This object will be used to initialize ResolvedFrame + instance to resolve the frames in the Schedule. **run_config: Extra arguments used to configure the run (e.g., for Aer configurable backends). Refer to the backend documentation for details on these arguments. @@ -159,7 +166,7 @@ def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule, qubit_lo_range, meas_lo_range, schedule_los, meas_level, meas_return, meas_map, memory_slot_size, - rep_time, parametric_pulses, + rep_time, parametric_pulses, frames_config, **run_config_common_dict) end_time = time() @@ -259,7 +266,7 @@ def _parse_pulse_args(backend, qubit_lo_freq, meas_lo_freq, qubit_lo_range, meas_lo_range, schedule_los, meas_level, meas_return, meas_map, memory_slot_size, - rep_time, parametric_pulses, + rep_time, parametric_pulses, frames_config, **run_config): """Build a pulse RunConfig replacing unset arguments with defaults derived from the `backend`. See `assemble` for more information on the required arguments. @@ -301,6 +308,18 @@ def _parse_pulse_args(backend, qubit_lo_freq, meas_lo_freq, qubit_lo_range, qubit_lo_range = qubit_lo_range or getattr(backend_config, 'qubit_lo_range', None) meas_lo_range = meas_lo_range or getattr(backend_config, 'meas_lo_range', None) + frames_config_ = None + if hasattr(backend_config, 'frames'): + frames_config_ = frames_configuration(backend_config.frames(), qubit_lo_freq) + + if frames_config is None: + frames_config = frames_config_ + else: + for index, config in frames_config_.items(): + # Do not override the frames provided by the user. + if index not in frames_config: + frames_config[index] = config + dynamic_reprate_enabled = getattr(backend_config, 'dynamic_reprate_enabled', False) rep_time = rep_time or getattr(backend_config, 'rep_times', None) @@ -326,6 +345,7 @@ def _parse_pulse_args(backend, qubit_lo_freq, meas_lo_freq, qubit_lo_range, memory_slot_size=memory_slot_size, rep_time=rep_time, parametric_pulses=parametric_pulses, + frames_config=frames_config, **run_config) run_config = RunConfig(**{k: v for k, v in run_config_dict.items() if v is not None}) @@ -453,3 +473,40 @@ def _expand_parameters(circuits, run_config): run_config.parameter_binds = [] return circuits, run_config + +def frames_configuration(frame_channels: List[List[PulseChannel]], + frame_frequencies: List[float], + frame_indices: List[int] = None) -> Union[dict, None]: + """ + Ties together the frames of the backend and the frequencies of the frames. + + Args: + frame_channels: A List of lists. Sublist i is a list of channel names + that frame i will broadcast on. + frame_frequencies: A list of starting frequencies for each frame. + frame_indices: The indices of the frames. If None is given these will be + in ascending order starting from 0. + + Returns: + frames_config: A dictionary with the frame index as key and the values are + a dict which can be used to initialized a ResolvedFrame. + """ + if len(frame_frequencies) != len(frame_channels): + raise QiskitError(f'Number of frames {len(frame_channels)} is incompatible with ' + f'the number of frame initial frequencies {len(frame_frequencies)}.') + + frames_config = {} + for idx, channels in enumerate(frame_channels): + if frame_indices: + index = frame_indices[idx] + else: + index = idx + + frames_config[index] = { + 'frame': Frame(index), + 'phase': 0.0, + 'frequency': frame_frequencies[idx], + 'channels': channels + } + + return frames_config From e0d2aacbc3d1452b5e4a72559c742667d6b599cd Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 5 Mar 2021 16:54:27 +0100 Subject: [PATCH 007/111] * Added call to frame resolution methodology in the assembler. --- qiskit/assembler/assemble_schedules.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index ab87a4cc4aaf..4a489f352a6a 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -106,7 +106,11 @@ def _assemble_experiments( else: formatted_schedules.append(pulse.Schedule(sched)) - compressed_schedules = transforms.compress_pulses(formatted_schedules) + frames_config = getattr(run_config, 'frames_config', None) + resolved_schedules = [transforms.resolve_frames(sched, frames_config) + for sched in formatted_schedules] + + compressed_schedules = transforms.compress_pulses(resolved_schedules) user_pulselib = {} experiments = [] From bb79fcbbb293ce30d006493ba679c78686315f5a Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 5 Mar 2021 16:56:15 +0100 Subject: [PATCH 008/111] * Added frame resolution methodology in transforms. * Made left and right alignment contexts sensitive to frames. --- qiskit/pulse/transforms.py | 147 +++++++++++++++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 5 deletions(-) diff --git a/qiskit/pulse/transforms.py b/qiskit/pulse/transforms.py index d032237dbe07..00ea2df8181b 100644 --- a/qiskit/pulse/transforms.py +++ b/qiskit/pulse/transforms.py @@ -17,7 +17,7 @@ from collections import defaultdict from copy import deepcopy from typing import Callable -from typing import List, Optional, Iterable, Union +from typing import Dict, List, Optional, Iterable, Tuple, Union import numpy as np @@ -26,6 +26,9 @@ from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap from qiskit.pulse.instructions import directives from qiskit.pulse.schedule import Schedule, ScheduleComponent +from qiskit.pulse.frame import Frame +from qiskit.pulse.resolved_frame import ResolvedFrame, ChannelTracker +from qiskit.pulse.library import Signal def align_measures(schedules: Iterable[Union['Schedule', instructions.Instruction]], @@ -318,8 +321,113 @@ def compress_pulses(schedules: List[Schedule]) -> List[Schedule]: return new_schedules +def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedule: + """ + Parse the schedule and replace instructions on Frames by instructions on the + appropriate channels. + + Args: + schedule: The schedule for which to replace frames with the appropriate + channels. + frames_config: A dictionary with the frame index as key and the values are + a dict which can be used to initialized a ResolvedFrame. + + Returns: + new_schedule: A new schedule where frames have been replaced with + their corresponding Drive, Control, and/or Measure channels. + + Raises: + PulseError: if a frame is not configured. + """ + if frames_config is None: + return schedule + + resolved_frames = {} + for frame_index, frame_settings in frames_config.items(): + frame = ResolvedFrame(**frame_settings) + frame.set_frame_instructions(schedule) + resolved_frames[frame.index] = frame + + # Used to keep track of the frequency and phase of the channels + channel_trackers = {} + for ch in schedule.channels: + if isinstance(ch, chans.PulseChannel): + channel_trackers[ch] = ChannelTracker(ch) + + # Add the channels that the frames broadcast on. + for frame in resolved_frames.values(): + for ch in frame.channels: + if ch not in channel_trackers: + channel_trackers[ch] = ChannelTracker(ch) + + sched = Schedule(name=schedule.name, metadata=schedule.metadata) + + for time, inst in schedule.instructions: + chan = inst.channel + + if isinstance(inst, instructions.Play): + if isinstance(inst.operands[0], Signal): + frame_idx = inst.operands[0].frame.index + + if frame_idx not in resolved_frames: + raise PulseError(f'{Frame(frame.index)} is not configured and cannot ' + f'be resolved.') + + frame = resolved_frames[frame_idx] + + frame_freq = frame.frequency(time) + frame_phase = frame.phase(time) + + # If the frequency and phase of the channel has already been set once in + # The past we compute shifts. + if channel_trackers[chan].is_initialized(): + freq_diff = frame_freq - channel_trackers[chan].frequency(time) + phase_diff = frame_phase - channel_trackers[chan].phase(time) + + if freq_diff != 0.0: + shift_freq = instructions.ShiftFrequency(freq_diff, chan) + sched.insert(time, shift_freq, inplace=True) + + if phase_diff != 0.0: + sched.insert(time, instructions.ShiftPhase(phase_diff, chan), inplace=True) + + # If the channel's phase and frequency has not been set in the past + # we set t now + else: + sched.insert(time, instructions.SetFrequency(frame_freq, chan), inplace=True) + sched.insert(time, instructions.SetPhase(frame_phase, chan), inplace=True) + + # Update the frequency and phase of this channel. + channel_trackers[chan].set_frequency(time, frame_freq) + channel_trackers[chan].set_phase(time, frame_phase) + + play = instructions.Play(inst.operands[0].pulse, chan) + sched.insert(time, play, inplace=True) + else: + sched.insert(time, instructions.Play(inst.pulse, chan), inplace=True) + + # Insert phase and frequency commands that are not applied to frames. + elif isinstance(inst, instructions.SetFrequency): + if isinstance(chan, chans.PulseChannel): + sched.insert(time, type(inst)(inst.frequency, chan)) + + elif isinstance(inst, instructions.ShiftFrequency): + if isinstance(chan, chans.PulseChannel): + sched.insert(time, type(inst)(inst.frequency, chan)) + + elif isinstance(inst, (instructions.SetPhase, instructions.ShiftPhase)): + if isinstance(chan, chans.PulseChannel): + sched.insert(time, type(inst)(inst.phase, chan)) + + else: + sched.insert(time, inst, inplace=True) + + return sched + + def _push_left_append(this: Schedule, other: Union['Schedule', instructions.Instruction], + ignore_frames: bool ) -> Schedule: r"""Return ``this`` with ``other`` inserted at the maximum time over all channels shared between ```this`` and ``other``. @@ -327,6 +435,9 @@ def _push_left_append(this: Schedule, Args: this: Input schedule to which ``other`` will be inserted. other: Other schedule to insert. + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. Returns: Push left appended schedule. @@ -334,6 +445,14 @@ def _push_left_append(this: Schedule, this_channels = set(this.channels) other_channels = set(other.channels) shared_channels = list(this_channels & other_channels) + + # Conservatively assume that a Frame instruction could impact all channels + if not ignore_frames: + for ch in this_channels | other_channels: + if isinstance(ch, Frame): + shared_channels = list(this_channels | other_channels) + break + ch_slacks = [this.stop_time - this.ch_stop_time(channel) + other.ch_start_time(channel) for channel in shared_channels] @@ -351,12 +470,15 @@ def _push_left_append(this: Schedule, return this.insert(insert_time, other, inplace=True) -def align_left(schedule: Schedule) -> Schedule: +def align_left(schedule: Schedule, ignore_frames: bool = False) -> Schedule: """Align a list of pulse instructions on the left. Args: schedule: Input schedule of which top-level ``child`` nodes will be rescheduled. + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. Returns: New schedule with input `schedule`` child schedules and instructions @@ -364,12 +486,13 @@ def align_left(schedule: Schedule) -> Schedule: """ aligned = Schedule() for _, child in schedule._children: - _push_left_append(aligned, child) + _push_left_append(aligned, child, ignore_frames) return aligned def _push_right_prepend(this: Union['Schedule', instructions.Instruction], other: Union['Schedule', instructions.Instruction], + ignore_frames: bool ) -> Schedule: r"""Return ``this`` with ``other`` inserted at the latest possible time such that ``other`` ends before it overlaps with any of ``this``. @@ -380,6 +503,9 @@ def _push_right_prepend(this: Union['Schedule', instructions.Instruction], Args: this: Input schedule to which ``other`` will be inserted. other: Other schedule to insert. + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. Returns: Push right prepended schedule. @@ -387,6 +513,14 @@ def _push_right_prepend(this: Union['Schedule', instructions.Instruction], this_channels = set(this.channels) other_channels = set(other.channels) shared_channels = list(this_channels & other_channels) + + # Conservatively assume that a Frame instruction could impact all channels + if not ignore_frames: + for ch in this_channels | other_channels: + if isinstance(ch, Frame): + shared_channels = list(this_channels | other_channels) + break + ch_slacks = [this.ch_start_time(channel) - other.ch_stop_time(channel) for channel in shared_channels] @@ -404,12 +538,15 @@ def _push_right_prepend(this: Union['Schedule', instructions.Instruction], return this -def align_right(schedule: Schedule) -> Schedule: +def align_right(schedule: Schedule, ignore_frames: bool = False) -> Schedule: """Align a list of pulse instructions on the right. Args: schedule: Input schedule of which top-level ``child`` nodes will be rescheduled. + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. Returns: New schedule with input `schedule`` child schedules and instructions @@ -417,7 +554,7 @@ def align_right(schedule: Schedule) -> Schedule: """ aligned = Schedule() for _, child in reversed(schedule._children): - aligned = _push_right_prepend(aligned, child) + aligned = _push_right_prepend(aligned, child, ignore_frames) return aligned From 70df61203449825509fae32ae45f3bfed5d754c0 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 5 Mar 2021 16:57:08 +0100 Subject: [PATCH 009/111] * Added the frames function to the backend configuration. --- .../providers/models/backendconfiguration.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/qiskit/providers/models/backendconfiguration.py b/qiskit/providers/models/backendconfiguration.py index 5a5f55ecb332..e8555046664f 100644 --- a/qiskit/providers/models/backendconfiguration.py +++ b/qiskit/providers/models/backendconfiguration.py @@ -769,6 +769,28 @@ def control(self, qubits: Iterable[int] = None, f"This backend - '{self.backend_name}' does not provide channel information." ) from ex + def frames(self) -> List[List[Channel]]: + """ + Gets the frames that the backend supports. + + Returns: + frames: A List of lists. Sublist i is a list of channel names that belong + to frame i. + """ + n_qubits = self.n_qubits + frames = [] + for qubit in range(n_qubits): + frame_ = [DriveChannel(qubit)] + for ctrl in range(n_qubits): + try: + frame_ += [ch for ch in self.control((ctrl, qubit))] + except BackendConfigurationError: + pass + + frames.append(frame_) + + return frames + def get_channel_qubits(self, channel: Channel) -> List[int]: """ Return a list of indices for qubits which are operated on directly by the given ``channel``. From 0db5c8d38b2f85f246967047a812e82a4afe8624 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 5 Mar 2021 17:02:17 +0100 Subject: [PATCH 010/111] * Added changes to instructions to support Frames and Signals. --- qiskit/pulse/instructions/frequency.py | 17 +++++++++-------- qiskit/pulse/instructions/instruction.py | 9 +++++---- qiskit/pulse/instructions/phase.py | 17 +++++++++-------- qiskit/pulse/instructions/play.py | 10 ++++++---- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/qiskit/pulse/instructions/frequency.py b/qiskit/pulse/instructions/frequency.py index 77730b9751b4..e03e9b2cb33c 100644 --- a/qiskit/pulse/instructions/frequency.py +++ b/qiskit/pulse/instructions/frequency.py @@ -17,6 +17,7 @@ from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.pulse.channels import PulseChannel +from qiskit.pulse.frame import Frame from qiskit.pulse.instructions.instruction import Instruction @@ -35,13 +36,13 @@ class SetFrequency(Instruction): """ def __init__(self, frequency: Union[float, ParameterExpression], - channel: PulseChannel, + channel: [PulseChannel, Frame], name: Optional[str] = None): """Creates a new set channel frequency instruction. Args: frequency: New frequency of the channel in Hz. - channel: The channel this instruction operates on. + channel: The channel or frame this instruction operates on. name: Name of this set channel frequency instruction. """ if not isinstance(frequency, ParameterExpression): @@ -54,8 +55,8 @@ def frequency(self) -> Union[float, ParameterExpression]: return self.operands[0] @property - def channel(self) -> PulseChannel: - """Return the :py:class:`~qiskit.pulse.channels.Channel` that this instruction is + def channel(self) -> [PulseChannel, Frame]: + """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ return self.operands[1] @@ -71,13 +72,13 @@ class ShiftFrequency(Instruction): def __init__(self, frequency: Union[float, ParameterExpression], - channel: PulseChannel, + channel: [PulseChannel, Frame], name: Optional[str] = None): """Creates a new shift frequency instruction. Args: frequency: Frequency shift of the channel in Hz. - channel: The channel this instruction operates on. + channel: The channel or frame this instruction operates on. name: Name of this set channel frequency instruction. """ if not isinstance(frequency, ParameterExpression): @@ -90,8 +91,8 @@ def frequency(self) -> Union[float, ParameterExpression]: return self.operands[0] @property - def channel(self) -> PulseChannel: - """Return the :py:class:`~qiskit.pulse.channels.Channel` that this instruction is + def channel(self) -> [PulseChannel, Frame]: + """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ return self.operands[1] diff --git a/qiskit/pulse/instructions/instruction.py b/qiskit/pulse/instructions/instruction.py index 3375e384f5e5..ac9e90c2cf58 100644 --- a/qiskit/pulse/instructions/instruction.py +++ b/qiskit/pulse/instructions/instruction.py @@ -24,12 +24,13 @@ import warnings from abc import ABC from collections import defaultdict -from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, Any +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, Any, Union from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType from qiskit.pulse.channels import Channel from qiskit.pulse.exceptions import PulseError from qiskit.pulse.utils import format_parameter_value +from qiskit.pulse.frame import Frame # pylint: disable=missing-return-doc @@ -43,14 +44,14 @@ class Instruction(ABC): def __init__(self, operands: Tuple, duration: int, - channels: Tuple[Channel], + channels: Union[Tuple[Channel], Tuple[Frame]], name: Optional[str] = None): """Instruction initializer. Args: operands: The argument list. duration: Deprecated. - channels: Tuple of pulse channels that this instruction operates on. + channels: Tuple of pulse channels or frames that this instruction operates on. name: Optional display name for this instruction. Raises: @@ -59,7 +60,7 @@ def __init__(self, type :class:`Channel`. """ for channel in channels: - if not isinstance(channel, Channel): + if not isinstance(channel, (Frame, Channel)): raise PulseError("Expected a channel, got {} instead.".format(channel)) if duration is not None: diff --git a/qiskit/pulse/instructions/phase.py b/qiskit/pulse/instructions/phase.py index 9a20e545ad20..ae6fcb878db2 100644 --- a/qiskit/pulse/instructions/phase.py +++ b/qiskit/pulse/instructions/phase.py @@ -19,6 +19,7 @@ from qiskit.circuit import ParameterExpression from qiskit.pulse.channels import PulseChannel +from qiskit.pulse.frame import Frame from qiskit.pulse.instructions.instruction import Instruction @@ -40,14 +41,14 @@ class ShiftPhase(Instruction): """ def __init__(self, phase: Union[complex, ParameterExpression], - channel: PulseChannel, + channel: [PulseChannel, Frame], name: Optional[str] = None): """Instantiate a shift phase instruction, increasing the output signal phase on ``channel`` by ``phase`` [radians]. Args: phase: The rotation angle in radians. - channel: The channel this instruction operates on. + channel: The channel or frame this instruction operates on. name: Display name for this instruction. """ super().__init__((phase, channel), None, (channel,), name=name) @@ -58,8 +59,8 @@ def phase(self) -> Union[complex, ParameterExpression]: return self.operands[0] @property - def channel(self) -> PulseChannel: - """Return the :py:class:`~qiskit.pulse.channels.Channel` that this instruction is + def channel(self) -> Union[PulseChannel, Frame]: + """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ return self.operands[1] @@ -85,14 +86,14 @@ class SetPhase(Instruction): def __init__(self, phase: Union[complex, ParameterExpression], - channel: PulseChannel, + channel: Union[PulseChannel, Frame], name: Optional[str] = None): """Instantiate a set phase instruction, setting the output signal phase on ``channel`` to ``phase`` [radians]. Args: phase: The rotation angle in radians. - channel: The channel this instruction operates on. + channel: The channel or frame this instruction operates on. name: Display name for this instruction. """ super().__init__((phase, channel), None, (channel,), name=name) @@ -103,8 +104,8 @@ def phase(self) -> Union[complex, ParameterExpression]: return self.operands[0] @property - def channel(self) -> PulseChannel: - """Return the :py:class:`~qiskit.pulse.channels.Channel` that this instruction is + def channel(self) -> Union[PulseChannel, Frame]: + """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ return self.operands[1] diff --git a/qiskit/pulse/instructions/play.py b/qiskit/pulse/instructions/play.py index 488166a71df3..6f7bd12f0b51 100644 --- a/qiskit/pulse/instructions/play.py +++ b/qiskit/pulse/instructions/play.py @@ -19,6 +19,7 @@ from qiskit.pulse.channels import PulseChannel from qiskit.pulse.exceptions import PulseError from qiskit.pulse.library.pulse import Pulse +from qiskit.pulse.library.signal import Signal from qiskit.pulse.instructions.instruction import Instruction @@ -31,7 +32,7 @@ class Play(Instruction): cycle time, dt, of the backend. """ - def __init__(self, pulse: Pulse, + def __init__(self, pulse: Union[Pulse, Signal], channel: PulseChannel, name: Optional[str] = None): """Create a new pulse instruction. @@ -45,8 +46,9 @@ def __init__(self, pulse: Pulse, Raises: PulseError: If pulse is not a Pulse type. """ - if not isinstance(pulse, Pulse): - raise PulseError("The `pulse` argument to `Play` must be of type `library.Pulse`.") + if not isinstance(pulse, (Pulse, Signal)): + raise PulseError("The `pulse` argument to `Play` must be of type `library.Pulse` or " + "`library.Signal`.") if name is None: name = pulse.name super().__init__((pulse, channel), None, (channel,), name=name) @@ -59,7 +61,7 @@ def __init__(self, pulse: Pulse, self._parameter_table[param].append(0) @property - def pulse(self) -> Pulse: + def pulse(self) -> [Pulse, Signal]: """A description of the samples that will be played.""" return self.operands[0] From 8163f39bc935b10ed0cbbd24da607148a29deba3 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 8 Mar 2021 10:44:48 +0100 Subject: [PATCH 011/111] * Fixed issue in the way non-frame Shift/Set instructions are handled in resolve_frames. --- qiskit/pulse/transforms.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/qiskit/pulse/transforms.py b/qiskit/pulse/transforms.py index 00ea2df8181b..0750f044bb9e 100644 --- a/qiskit/pulse/transforms.py +++ b/qiskit/pulse/transforms.py @@ -407,16 +407,12 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu sched.insert(time, instructions.Play(inst.pulse, chan), inplace=True) # Insert phase and frequency commands that are not applied to frames. - elif isinstance(inst, instructions.SetFrequency): - if isinstance(chan, chans.PulseChannel): + elif isinstance(type(inst), (instructions.SetFrequency, instructions.ShiftFrequency)): + if issubclass(chan, chans.PulseChannel): sched.insert(time, type(inst)(inst.frequency, chan)) - elif isinstance(inst, instructions.ShiftFrequency): - if isinstance(chan, chans.PulseChannel): - sched.insert(time, type(inst)(inst.frequency, chan)) - - elif isinstance(inst, (instructions.SetPhase, instructions.ShiftPhase)): - if isinstance(chan, chans.PulseChannel): + elif isinstance(type(inst), (instructions.SetPhase, instructions.ShiftPhase)): + if issubclass(chan, chans.PulseChannel): sched.insert(time, type(inst)(inst.phase, chan)) else: From 6e91e9505ce495c86014d3269ab33ba1dc4fb765 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 8 Mar 2021 13:25:06 +0100 Subject: [PATCH 012/111] * Added code to serialize frames config in the assembler. * Modified docstring. --- qiskit/assembler/assemble_schedules.py | 7 +++++++ qiskit/pulse/library/signal.py | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index 4a489f352a6a..0274e9219a3d 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -305,4 +305,11 @@ def _assemble_config(lo_converter: converters.LoConfigConverter, if m_los: qobj_config['meas_lo_freq'] = [freq / 1e9 for freq in m_los] + # frames config + frames_config = qobj_config.get('frames_config', None) + if frames_config: + for frame_idx, frame_config in frames_config.items(): + frame_config['channels'] = [ch.name for ch in frame_config['channels']] + frame_config['frame'] = frame_config['frame'].name + return qobj.PulseQobjConfig(**qobj_config) diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index 4f6cd8737121..165444050a09 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -21,7 +21,10 @@ class Signal: - """A Signal is a Pulse played in a given frame.""" + """ + A Signal is a Pulse, i.e. a complex-valued waveform envelope, played in a given + Frame, i.e. a frequency and a phase. + """ def __init__(self, pulse: Pulse, frame: Frame, name: Optional[str] = None): """ From a38eb73585bb3afec3374e1a118979b00d143d6c Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 8 Mar 2021 14:35:41 +0100 Subject: [PATCH 013/111] * Lint fix. --- qiskit/compiler/assembler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index 9c0c79419ec4..057ef8041b46 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -474,6 +474,7 @@ def _expand_parameters(circuits, run_config): return circuits, run_config + def frames_configuration(frame_channels: List[List[PulseChannel]], frame_frequencies: List[float], frame_indices: List[int] = None) -> Union[dict, None]: From 175d0377ca28aaa33532e47787eb1de56e8cdf42 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 8 Mar 2021 16:55:03 +0100 Subject: [PATCH 014/111] * Lint fixes. --- qiskit/assembler/assemble_schedules.py | 2 +- qiskit/compiler/assembler.py | 4 ++++ qiskit/pulse/frame.py | 2 +- qiskit/pulse/library/signal.py | 4 ++-- qiskit/pulse/resolved_frame.py | 4 ++++ qiskit/pulse/transforms.py | 4 ++-- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index 0274e9219a3d..ebcd8644ecc4 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -308,7 +308,7 @@ def _assemble_config(lo_converter: converters.LoConfigConverter, # frames config frames_config = qobj_config.get('frames_config', None) if frames_config: - for frame_idx, frame_config in frames_config.items(): + for frame_config in frames_config.values(): frame_config['channels'] = [ch.name for ch in frame_config['channels']] frame_config['frame'] = frame_config['frame'].name diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index 057ef8041b46..f09e1c9e1282 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -491,6 +491,10 @@ def frames_configuration(frame_channels: List[List[PulseChannel]], Returns: frames_config: A dictionary with the frame index as key and the values are a dict which can be used to initialized a ResolvedFrame. + + Raises: + QiskitError: if the number of frame frequencies is not the same as the number + of frames, i.e. the length of frame_channels. """ if len(frame_frequencies) != len(frame_channels): raise QiskitError(f'Number of frames {len(frame_channels)} is incompatible with ' diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 0fa715a3ae47..4e361712adcc 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -84,7 +84,7 @@ def assign(self, parameter: Parameter, value: ParameterValueType) -> 'Frame': value: The new value to bind to. Returns: - A new frame with updated parameters. + Frame: A new frame with updated parameters. Raises: PulseError: If the parameter is not present in the frame. diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index 165444050a09..7f6e046c616d 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -44,7 +44,7 @@ def __init__(self, pulse: Pulse, frame: Frame, name: Optional[str] = None): self.name = name @property - def id(self) -> int: + def id(self) -> int: # pylint: disable=invalid-name """Unique identifier for this signal.""" return id(self) @@ -86,7 +86,7 @@ def assign_parameters(self, Parameter expression. Returns: - New signal with updated parameters. + Signal: a new signal with updated parameters. """ pulse = self._pulse.assign_parameters(value_dict) frame = self._frame diff --git a/qiskit/pulse/resolved_frame.py b/qiskit/pulse/resolved_frame.py index 55f981355194..e6345231a990 100644 --- a/qiskit/pulse/resolved_frame.py +++ b/qiskit/pulse/resolved_frame.py @@ -152,6 +152,10 @@ def set_frame_instructions(self, schedule: Schedule): Args: schedule: The schedule from which to extract frame operations. + + Raises: + PulseError: if the internal filtering does not contain the right + instructions. """ frame_instruction_types = [ShiftPhase, SetPhase, ShiftFrequency, SetFrequency] frame_instructions = schedule.filter(instruction_types=frame_instruction_types) diff --git a/qiskit/pulse/transforms.py b/qiskit/pulse/transforms.py index 0750f044bb9e..bded69f001e4 100644 --- a/qiskit/pulse/transforms.py +++ b/qiskit/pulse/transforms.py @@ -17,7 +17,7 @@ from collections import defaultdict from copy import deepcopy from typing import Callable -from typing import Dict, List, Optional, Iterable, Tuple, Union +from typing import Dict, List, Optional, Iterable, Union import numpy as np @@ -343,7 +343,7 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu return schedule resolved_frames = {} - for frame_index, frame_settings in frames_config.items(): + for frame_settings in frames_config.values(): frame = ResolvedFrame(**frame_settings) frame.set_frame_instructions(schedule) resolved_frames[frame.index] = frame From 597181fae2a31813be0f45d2ea40ce27a403b4d9 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 8 Mar 2021 19:23:31 +0100 Subject: [PATCH 015/111] * Lint fix. --- qiskit/providers/models/backendconfiguration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/providers/models/backendconfiguration.py b/qiskit/providers/models/backendconfiguration.py index e8555046664f..5f68f7109d81 100644 --- a/qiskit/providers/models/backendconfiguration.py +++ b/qiskit/providers/models/backendconfiguration.py @@ -783,7 +783,7 @@ def frames(self) -> List[List[Channel]]: frame_ = [DriveChannel(qubit)] for ctrl in range(n_qubits): try: - frame_ += [ch for ch in self.control((ctrl, qubit))] + frame_ += list(self.control((ctrl, qubit))) except BackendConfigurationError: pass From 0a83f72659cc60a57c33f3c6654745466af4c4c3 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 9 Mar 2021 18:32:30 +0100 Subject: [PATCH 016/111] * Added methodology to track the phase accumulation. --- qiskit/compiler/assembler.py | 9 ++- qiskit/pulse/resolved_frame.py | 132 +++++++++++---------------------- qiskit/pulse/transforms.py | 15 +++- 3 files changed, 61 insertions(+), 95 deletions(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index d1d77e588795..2c8b4069a41a 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -310,7 +310,9 @@ def _parse_pulse_args(backend, qubit_lo_freq, meas_lo_freq, qubit_lo_range, frames_config_ = None if hasattr(backend_config, 'frames'): - frames_config_ = frames_configuration(backend_config.frames(), qubit_lo_freq) + frames_config_ = frames_configuration(backend_config.frames(), + qubit_lo_freq, + backend_config.dt) if frames_config is None: frames_config = frames_config_ @@ -476,6 +478,7 @@ def _expand_parameters(circuits, run_config): def frames_configuration(frame_channels: List[List[PulseChannel]], frame_frequencies: List[float], + sample_duration: float, frame_indices: List[int] = None) -> Union[dict, None]: """ Ties together the frames of the backend and the frequencies of the frames. @@ -484,6 +487,7 @@ def frames_configuration(frame_channels: List[List[PulseChannel]], frame_channels: A List of lists. Sublist i is a list of channel names that frame i will broadcast on. frame_frequencies: A list of starting frequencies for each frame. + sample_duration: time of a sample. frame_indices: The indices of the frames. If None is given these will be in ascending order starting from 0. @@ -510,7 +514,8 @@ def frames_configuration(frame_channels: List[List[PulseChannel]], 'frame': Frame(index), 'phase': 0.0, 'frequency': frame_frequencies[idx], - 'channels': channels + 'channels': channels, + 'sample_duration': sample_duration } return frames_config diff --git a/qiskit/pulse/resolved_frame.py b/qiskit/pulse/resolved_frame.py index e6345231a990..6fbc2a8167dd 100644 --- a/qiskit/pulse/resolved_frame.py +++ b/qiskit/pulse/resolved_frame.py @@ -13,10 +13,11 @@ """Implements a Frame.""" from abc import ABC -from typing import Dict, List, Union +from typing import List, Union +import numpy as np from qiskit.circuit import Parameter -from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType +from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.pulse.channels import Channel, PulseChannel from qiskit.pulse.frame import Frame from qiskit.pulse.schedule import Schedule @@ -31,16 +32,17 @@ class Tracker(ABC): or a pulse channel in a given schedule. """ - def __init__(self, index: Union[int, Parameter]): + def __init__(self, index: Union[int, Parameter], sample_duration: float): """ Args: index: The index of the Tracker. Corresponds to the index of a - :class:`~qiskit.pulse.Frame` or a channel. + :class:`~qiskit.pulse.Frame` or a channel. + sample_duration: Duration of a sample. """ self._index = index - self._frequencies = [] - self._phases = [] + self._frequencies_phases = [] # Tuple of (time, frequency, phase) self._instructions = {} + self._sample_duration = sample_duration self._parameters = set() if isinstance(index, ParameterExpression): @@ -61,8 +63,8 @@ def frequency(self, time: int) -> float: Returns: frequency: The frequency of the frame up until time. """ - frequency = self._frequencies[0][1] - for time_freq in self._frequencies: + frequency = self._frequencies_phases[0][1] + for time_freq in self._frequencies_phases: if time_freq[0] <= time: frequency = time_freq[1] else: @@ -80,42 +82,44 @@ def phase(self, time: int) -> float: Returns: phase: The phase of the frame up until time. """ - phase = self._phases[0][1] - for time_freq in self._phases: + if len(self._frequencies_phases) == 0: + return 0.0 + else: + phase = self._frequencies_phases[0][1] + last_time = self._frequencies_phases[0][0] + + for time_freq in self._frequencies_phases: if time_freq[0] <= time: - phase = time_freq[1] + phase = time_freq[2] + last_time = time_freq[0] else: break - return phase + freq = self.frequency(time) + + return (phase + 2*np.pi*freq*(time-last_time)*self._sample_duration) % (2*np.pi) def set_frequency(self, time: int, frequency: float): """Insert a new frequency in the time-ordered frequencies.""" - - if time > 0 and time in set([_[0] for _ in self._frequencies]): - if self.frequency(time) != frequency: - raise PulseError(f'Frequency already added at time {time}.') - insert_idx = 0 - for idx, time_freq in enumerate(self._frequencies): + for idx, time_freq in enumerate(self._frequencies_phases): if time_freq[0] < time: insert_idx = idx - self._frequencies.insert(insert_idx + 1, (time, frequency)) + phase = self.phase(time) + + self._frequencies_phases.insert(insert_idx + 1, (time, frequency, phase)) def set_phase(self, time: int, phase: float): """Insert a new phase in the time-ordered phases.""" - - if time > 0 and time in set([_[0] for _ in self._phases]): - if self.phase(time) != phase: - raise PulseError(f'Phase already added at time {time} for Frame({self.index}).') - insert_idx = 0 - for idx, time_freq in enumerate(self._phases): + for idx, time_freq in enumerate(self._frequencies_phases): if time_freq[0] < time: insert_idx = idx - self._phases.insert(insert_idx + 1, (time, phase)) + frequency = self.frequency(time) + + self._frequencies_phases.insert(insert_idx + 1, (time, frequency, phase)) class ResolvedFrame(Tracker): @@ -125,17 +129,23 @@ class ResolvedFrame(Tracker): """ def __init__(self, frame: Frame, frequency: float, phase: float, - channels: List[Channel]): + sample_duration: float, channels: List[Channel]): """ Args: frame: The frame to track. frequency: The initial frequency of the frame. phase: The initial phase of the frame. + sample_duration: Duration of a sample. channels: The list of channels on which the frame instructions apply. + + Raises: + PulseError: If there are still parameters in the given frame. """ - super().__init__(frame.index) - self._frequencies = [(0, frequency)] - self._phases = [(0, phase)] + if frame.is_parameterized(): + raise PulseError('A parameterized frame cannot be given to ResolvedFrame.') + + super().__init__(frame.index, sample_duration) + self._frequencies_phases = [(0, frequency, phase)] self._channels = channels for ch in self._channels: @@ -173,63 +183,6 @@ def set_frame_instructions(self, schedule: Schedule): else: raise PulseError('Unexpected frame operation.') - def assign(self, parameter: Parameter, value: ParameterValueType) -> 'ResolvedFrame': - """ - Override the base class's assign method to handle any links between the - parameter of the frame and the parameters of the sub-channels. - - Args: - parameter: The parameter to assign - value: The value of the parameter. - """ - return self.assign_parameters({parameter: value}) - - def assign_parameters(self, - value_dict: Dict[ParameterExpression, ParameterValueType] - ) -> 'ResolvedFrame': - """ - Assign the value of the parameters. - - Args: - value_dict: A mapping from Parameters to either numeric values or another - Parameter expression. - """ - assigned_sub_channels = self._assign_sub_channels(value_dict) - - new_index = None - if isinstance(self._index, ParameterExpression): - for param, value in value_dict.items(): - if param in self._index.parameters: - new_index = self._index.assign(param, value) - if not new_index.parameters: - new_index = int(new_index) - - if new_index is not None: - return type(self)(new_index, self._frequencies, self._phases, assigned_sub_channels) - - return type(self)(self._index, self._frequencies, self._phases, assigned_sub_channels) - - def _assign_sub_channels(self, value_dict: Dict[ParameterExpression, - ParameterValueType]) -> List['Channel']: - """ - Args: - value_dict: The keys are the parameters to assign and the values are the - values of the parameters. - - Returns: - Frame: A Frame in which the parameter has been assigned. - """ - sub_channels = [] - for ch in self._channels: - if isinstance(ch.index, ParameterExpression): - for param, value in value_dict.items(): - if param in ch.parameters: - ch = ch.assign(param, value) - - sub_channels.append(ch) - - return sub_channels - def __repr__(self): sub_str = '[' + ', '.join([ch.__repr__() for ch in self._channels]) + ']' return f'{self.__class__.__name__}({self._index}, {sub_str})' @@ -238,12 +191,13 @@ def __repr__(self): class ChannelTracker(Tracker): """Class to track the phase and frequency of channels when resolving frames.""" - def __init__(self, channel: PulseChannel): + def __init__(self, channel: PulseChannel, sample_duration: float): """ Args: channel: The channel that this tracker tracks. + sample_duration: Duration of a sample. """ - super().__init__(channel.index) + super().__init__(channel.index, sample_duration) self._channel = channel self._frequencies = [] self._phases = [] diff --git a/qiskit/pulse/transforms.py b/qiskit/pulse/transforms.py index bded69f001e4..9f72d49f730a 100644 --- a/qiskit/pulse/transforms.py +++ b/qiskit/pulse/transforms.py @@ -343,22 +343,27 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu return schedule resolved_frames = {} + sample_duration = None for frame_settings in frames_config.values(): frame = ResolvedFrame(**frame_settings) frame.set_frame_instructions(schedule) resolved_frames[frame.index] = frame + sample_duration = frame_settings['sample_duration'] + + if sample_duration is None: + raise PulseError('Frame configuration does not have a sample duration.') # Used to keep track of the frequency and phase of the channels channel_trackers = {} for ch in schedule.channels: if isinstance(ch, chans.PulseChannel): - channel_trackers[ch] = ChannelTracker(ch) + channel_trackers[ch] = ChannelTracker(ch, sample_duration) # Add the channels that the frames broadcast on. for frame in resolved_frames.values(): for ch in frame.channels: if ch not in channel_trackers: - channel_trackers[ch] = ChannelTracker(ch) + channel_trackers[ch] = ChannelTracker(ch, sample_duration) sched = Schedule(name=schedule.name, metadata=schedule.metadata) @@ -392,7 +397,7 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu sched.insert(time, instructions.ShiftPhase(phase_diff, chan), inplace=True) # If the channel's phase and frequency has not been set in the past - # we set t now + # we set it now else: sched.insert(time, instructions.SetFrequency(frame_freq, chan), inplace=True) sched.insert(time, instructions.SetPhase(frame_phase, chan), inplace=True) @@ -415,7 +420,9 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu if issubclass(chan, chans.PulseChannel): sched.insert(time, type(inst)(inst.phase, chan)) - else: + if isinstance(type(inst), (instructions.Delay, instructions.Call, + instructions.Snapshot, instructions.Acquire, + instructions.Directive)): sched.insert(time, inst, inplace=True) return sched From 3506063beed43f4f77c52e93443245a3a3c1dac4 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 10 Mar 2021 11:13:01 +0100 Subject: [PATCH 017/111] * Added type hint to builder. * Added basic unittests. --- qiskit/pulse/builder.py | 2 +- test/python/pulse/test_builder.py | 63 ++++++++++++++++++++++++++ test/python/pulse/test_instructions.py | 14 +++++- test/python/pulse/test_pulse_lib.py | 13 +++++- 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index 6ad2f8a3d27d..d28098243d1c 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -1448,7 +1448,7 @@ def delay(duration: int, append_instruction(instructions.Delay(duration, channel, name=name)) -def play(pulse: Union[library.Pulse, np.ndarray], +def play(pulse: Union[library.Pulse, library.Signal, np.ndarray], channel: chans.PulseChannel, name: Optional[str] = None): """Play a ``pulse`` on a ``channel``. diff --git a/test/python/pulse/test_builder.py b/test/python/pulse/test_builder.py index 8be6d589b108..6141b2d0711e 100644 --- a/test/python/pulse/test_builder.py +++ b/test/python/pulse/test_builder.py @@ -1197,3 +1197,66 @@ def test_call_with_parameter_name_collision(self): self.assertEqual(play_0.pulse.sigma, 40) self.assertEqual(play_1.pulse.amp, 0.2) self.assertEqual(play_1.pulse.sigma, 40) + + +class TestBuilderFrames(TestBuilder): + """Test that the builder works with frames.""" + + def test_simple_frame_schedule(self): + """Test basic schedule construction with frames.""" + + signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(0)) + with pulse.build() as sched: + with pulse.align_left(): + pulse.play(signal, pulse.DriveChannel(0)) + pulse.shift_phase(1.57, pulse.Frame(0)) + pulse.play(signal, pulse.DriveChannel(0)) + + play_gaus = pulse.Play(signal, pulse.DriveChannel(0)) + self.assertEqual(sched.instructions[0][0], 0) + self.assertEqual(sched.instructions[0][1], play_gaus) + self.assertEqual(sched.instructions[1][0], 160) + self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame(0))) + self.assertEqual(sched.instructions[2][0], 160) + self.assertEqual(sched.instructions[2][1], play_gaus) + + def test_ignore_frames(self): + """Test the behavior of ignoring frames in the alignments context.""" + + signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(0)) + + with pulse.build() as sched: + with pulse.align_right(ignore_frames=True): + pulse.play(signal, pulse.DriveChannel(0)) + pulse.shift_phase(1.57, pulse.Frame(0)) + pulse.shift_phase(1.57, pulse.Frame(1)) + pulse.play(signal, pulse.DriveChannel(0)) + + play_gaus = pulse.Play(signal, pulse.DriveChannel(0)) + + self.assertEqual(sched.instructions[0][0], 0) + self.assertEqual(sched.instructions[0][1], play_gaus) + self.assertEqual(sched.instructions[1][0], 160) + self.assertEqual(sched.instructions[1][1], play_gaus) + self.assertEqual(sched.instructions[2][0], 320) + self.assertEqual(sched.instructions[2][1], pulse.ShiftPhase(1.57, pulse.Frame(0))) + self.assertEqual(sched.instructions[3][0], 320) + self.assertEqual(sched.instructions[3][1], pulse.ShiftPhase(1.57, pulse.Frame(1))) + + with pulse.build() as sched: + with pulse.align_right(ignore_frames=False): + pulse.play(signal, pulse.DriveChannel(0)) + pulse.shift_phase(1.57, pulse.Frame(0)) + pulse.shift_phase(1.57, pulse.Frame(1)) + pulse.play(signal, pulse.DriveChannel(0)) + + play_gaus = pulse.Play(signal, pulse.DriveChannel(0)) + + self.assertEqual(sched.instructions[0][0], 0) + self.assertEqual(sched.instructions[0][1], play_gaus) + self.assertEqual(sched.instructions[1][0], 160) + self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame(0))) + self.assertEqual(sched.instructions[2][0], 160) + self.assertEqual(sched.instructions[2][1], pulse.ShiftPhase(1.57, pulse.Frame(1))) + self.assertEqual(sched.instructions[3][0], 160) + self.assertEqual(sched.instructions[3][1], play_gaus) diff --git a/test/python/pulse/test_instructions.py b/test/python/pulse/test_instructions.py index 0b35c06ccf18..713515a862e2 100644 --- a/test/python/pulse/test_instructions.py +++ b/test/python/pulse/test_instructions.py @@ -15,7 +15,7 @@ import numpy as np from qiskit import pulse, circuit -from qiskit.pulse import channels, configuration, instructions, library +from qiskit.pulse import channels, configuration, instructions, library, Frame from qiskit.test import QiskitTestCase @@ -149,6 +149,12 @@ def test_freq(self): self.assertEqual(repr(set_freq), "SetFrequency(4500000000.0, DriveChannel(1), name='test')") + def test_frame(self): + """Test the basic shift phase on a Frame.""" + set_freq = instructions.SetFrequency(4.5e9, Frame(123)) + + self.assertEqual(set_freq.channel, Frame(123)) + class TestShiftPhase(QiskitTestCase): """Test the instruction construction.""" @@ -172,6 +178,12 @@ def test_default(self): name='test')) self.assertEqual(repr(shift_phase), "ShiftPhase(1.57, DriveChannel(0))") + def test_frame(self): + """Test the basic shift phase on a Frame.""" + shift_phase = instructions.ShiftPhase(1.57, Frame(123)) + + self.assertEqual(shift_phase.channel, Frame(123)) + class TestSnapshot(QiskitTestCase): """Snapshot tests.""" diff --git a/test/python/pulse/test_pulse_lib.py b/test/python/pulse/test_pulse_lib.py index 71863247a834..236b88417115 100644 --- a/test/python/pulse/test_pulse_lib.py +++ b/test/python/pulse/test_pulse_lib.py @@ -16,8 +16,9 @@ import numpy as np from qiskit.pulse.library import (Waveform, Constant, Gaussian, GaussianSquare, Drag, - gaussian, gaussian_square, drag as pl_drag) + Signal, gaussian, gaussian_square, drag as pl_drag) +from qiskit.pulse.frame import Frame from qiskit.pulse import functional_pulse, PulseError from qiskit.test import QiskitTestCase @@ -88,6 +89,16 @@ def test_pulse_limits(self): self.fail('Waveform incorrectly failed to approximately unit norm samples.') +class TestSignal(QiskitTestCase): + """Tests for Signals.""" + + def test_construction(self): + """Test the basics of the signal class.""" + signal = Signal(Gaussian(160, 0.1, 40), Frame(123)) + self.assertEqual(signal.pulse, Gaussian(160, 0.1, 40)) + self.assertEqual(signal.frame, Frame(123)) + + class TestParametricPulses(QiskitTestCase): """Tests for all subclasses of ParametricPulse.""" From 81da0365f30b3d89392e13902a6d88485e7fd79e Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 10 Mar 2021 14:03:27 +0100 Subject: [PATCH 018/111] * Tests for frame resolving and phase advance calculations. --- test/python/pulse/test_frames.py | 129 +++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 test/python/pulse/test_frames.py diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py new file mode 100644 index 000000000000..90b317ad1f25 --- /dev/null +++ b/test/python/pulse/test_frames.py @@ -0,0 +1,129 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test cases for frames in Qiskit pulse.""" + +import numpy as np + +from qiskit.compiler.assembler import frames_configuration +import qiskit.pulse as pulse +from qiskit.test import QiskitTestCase +from qiskit.pulse.transforms import resolve_frames +from qiskit.pulse.resolved_frame import ResolvedFrame + + +class TestResolvedFrames(QiskitTestCase): + """Test that resolved frames behave properly.""" + + def setUp(self): + super().setUp() + self.dt = 2.2222222222222221e-10 + self.f0 = 0.093e9 # Frequency of frame 0 + self.f1 = 0.125e9 # Frequency of frame 1 + + def test_phase_advance(self): + """Test that phases are properly set when frames are resolved.""" + + d0 = pulse.DriveChannel(0) + sig0 = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(0)) + sig1 = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(1)) + + with pulse.build() as sched: + pulse.play(sig0, d0) + pulse.play(sig1, d0) + pulse.play(sig0, d0) + pulse.play(sig1, d0) + + frames_config = frames_configuration([[d0], [d0]], [self.f0, self.f1], self.dt) + + frame0_ = ResolvedFrame(pulse.Frame(1), self.f0, 0.0, self.dt, []) + frame1_ = ResolvedFrame(pulse.Frame(2), self.f1, 0.0, self.dt, []) + + # Check that the resolved frames are tracking phases properly + for time in [0, 160, 320, 480]: + phase = np.angle(np.exp(2.0j*np.pi*self.f0*time*self.dt)) % (2*np.pi) + self.assertAlmostEqual(frame0_.phase(time), phase, places=8) + + phase = np.angle(np.exp(2.0j * np.pi * self.f1 * time * self.dt)) % (2 * np.pi) + self.assertAlmostEqual(frame1_.phase(time), phase, places=8) + + # Check that the proper phase instructions are added to the frame resolved schedules. + resolved = resolve_frames(sched, frames_config).instructions + + params = [(0, self.f0, 1), (160, self.f1, 4), (320, self.f0, 7), (480, self.f1, 10)] + + for time, frame_frequency, index in params: + phase = np.angle(np.exp(2.0j*np.pi*frame_frequency*time*self.dt)) % (2*np.pi) + self.assertEqual(resolved[index][0], time) + self.assertAlmostEqual(resolved[index][1].phase, phase, places=8) + + def test_phase_advance_with_instructions(self): + """Test that the phase advances are properly computed with frame instructions.""" + + sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(0)) + + with pulse.build() as sched: + pulse.play(sig, pulse.DriveChannel(0)) + pulse.shift_phase(1.0, pulse.Frame(0)) + + frame = ResolvedFrame(pulse.Frame(0), self.f0, 0.0, self.dt, []) + frame.set_frame_instructions(sched) + + self.assertAlmostEqual(frame.phase(0), 0.0, places=8) + + # Test the phase right before the shift phase instruction + phase = np.angle(np.exp(2.0j*np.pi*self.f0*159*self.dt)) % (2*np.pi) + self.assertAlmostEqual(frame.phase(159), phase, places=8) + + # Test the phase at and after the shift phase instruction + phase = (np.angle(np.exp(2.0j * np.pi * self.f0 * 160 * self.dt)) + 1.0) % (2 * np.pi) + self.assertAlmostEqual(frame.phase(160), phase, places=8) + + phase = (np.angle(np.exp(2.0j * np.pi * self.f0 * 161 * self.dt)) + 1.0) % (2 * np.pi) + self.assertAlmostEqual(frame.phase(161), phase, places=8) + + def test_set_frequency(self): + """Test setting the frequency of the resolved frame.""" + + frame = ResolvedFrame(pulse.Frame(0), self.f1, 0.0, self.dt, []) + frame.set_frequency(16, self.f0) + frame.set_frequency(10, self.f1) + frame.set_frequency(10, self.f1) + + self.assertAlmostEqual(frame.frequency(0), self.f1) + self.assertAlmostEqual(frame.frequency(10), self.f1) + self.assertAlmostEqual(frame.frequency(15), self.f1) + self.assertAlmostEqual(frame.frequency(16), self.f0) + + def test_broadcasting(self): + """Test that resolved frames broadcast to control channels.""" + + sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(0)) + + with pulse.build() as sched: + with pulse.align_left(): + pulse.play(sig, pulse.DriveChannel(3)) + pulse.shift_phase(1.23, pulse.Frame(0)) + with pulse.align_left(ignore_frames=True): + pulse.play(sig, pulse.DriveChannel(3)) + pulse.play(sig, pulse.ControlChannel(0)) + + frames_config = frames_configuration([[pulse.DriveChannel(3), pulse.ControlChannel(0)]], + [self.f0], self.dt) + + resolved = resolve_frames(sched, frames_config).instructions + + phase = (np.angle(np.exp(2.0j * np.pi * self.f0 * 160 * self.dt)) + 1.23) % (2 * np.pi) + + self.assertAlmostEqual(resolved[1][1].phase, 0.0, places=8) + self.assertAlmostEqual(resolved[4][1].phase, phase, places=8) + self.assertAlmostEqual(resolved[6][1].phase, phase, places=8) From 0f35a0e88ca127dc8818fadf03501e9f7d58e46c Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 10 Mar 2021 14:11:02 +0100 Subject: [PATCH 019/111] * Added a few more basic tests. --- test/python/pulse/test_frames.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 90b317ad1f25..4f9904667afa 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -14,6 +14,7 @@ import numpy as np +from qiskit.circuit import Parameter from qiskit.compiler.assembler import frames_configuration import qiskit.pulse as pulse from qiskit.test import QiskitTestCase @@ -21,6 +22,23 @@ from qiskit.pulse.resolved_frame import ResolvedFrame +class TestFrame(QiskitTestCase): + + def basic(self): + """Test basic functionality of frames.""" + + frame = pulse.Frame(123) + self.assertEqual(frame.index, 123) + self.assertEqual(frame.name, 'f123') + + def test_parameter(self): + """Test parameter assignment.""" + param = Parameter('a') + frame = pulse.Frame(param) + self.assertTrue(frame.is_parameterized()) + self.assertEqual(frame.assign(param, 123), pulse.Frame(123)) + + class TestResolvedFrames(QiskitTestCase): """Test that resolved frames behave properly.""" @@ -99,10 +117,10 @@ def test_set_frequency(self): frame.set_frequency(10, self.f1) frame.set_frequency(10, self.f1) - self.assertAlmostEqual(frame.frequency(0), self.f1) - self.assertAlmostEqual(frame.frequency(10), self.f1) - self.assertAlmostEqual(frame.frequency(15), self.f1) - self.assertAlmostEqual(frame.frequency(16), self.f0) + self.assertAlmostEqual(frame.frequency(0), self.f1, places=8) + self.assertAlmostEqual(frame.frequency(10), self.f1, places=8) + self.assertAlmostEqual(frame.frequency(15), self.f1, places=8) + self.assertAlmostEqual(frame.frequency(16), self.f0, places=8) def test_broadcasting(self): """Test that resolved frames broadcast to control channels.""" From c510dc4ed64a7453ee569436693d087d6efef922 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 10 Mar 2021 15:06:37 +0100 Subject: [PATCH 020/111] * Fixed bug in transforms.resolve_frames. --- qiskit/pulse/transforms.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/qiskit/pulse/transforms.py b/qiskit/pulse/transforms.py index 9f72d49f730a..5ae05327dbf4 100644 --- a/qiskit/pulse/transforms.py +++ b/qiskit/pulse/transforms.py @@ -412,15 +412,15 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu sched.insert(time, instructions.Play(inst.pulse, chan), inplace=True) # Insert phase and frequency commands that are not applied to frames. - elif isinstance(type(inst), (instructions.SetFrequency, instructions.ShiftFrequency)): - if issubclass(chan, chans.PulseChannel): - sched.insert(time, type(inst)(inst.frequency, chan)) + elif isinstance(inst, (instructions.SetFrequency, instructions.ShiftFrequency)): + if issubclass(type(chan), chans.PulseChannel): + sched.insert(time, type(inst)(inst.frequency, chan), inplace=True) - elif isinstance(type(inst), (instructions.SetPhase, instructions.ShiftPhase)): - if issubclass(chan, chans.PulseChannel): - sched.insert(time, type(inst)(inst.phase, chan)) + elif isinstance(inst, (instructions.SetPhase, instructions.ShiftPhase)): + if issubclass(type(chan), chans.PulseChannel): + sched.insert(time, type(inst)(inst.phase, chan), inplace=True) - if isinstance(type(inst), (instructions.Delay, instructions.Call, + if isinstance(inst, (instructions.Delay, instructions.Call, instructions.Snapshot, instructions.Acquire, instructions.Directive)): sched.insert(time, inst, inplace=True) From 23ee452a61a7f12810f3ac1808370266de7d66c2 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 10 Mar 2021 15:24:11 +0100 Subject: [PATCH 021/111] * Lint fix. --- qiskit/pulse/transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/pulse/transforms.py b/qiskit/pulse/transforms.py index 5ae05327dbf4..1dafb111f122 100644 --- a/qiskit/pulse/transforms.py +++ b/qiskit/pulse/transforms.py @@ -421,8 +421,8 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu sched.insert(time, type(inst)(inst.phase, chan), inplace=True) if isinstance(inst, (instructions.Delay, instructions.Call, - instructions.Snapshot, instructions.Acquire, - instructions.Directive)): + instructions.Snapshot, instructions.Acquire, + instructions.Directive)): sched.insert(time, inst, inplace=True) return sched From fc30007de6d0866997e6eeb753080c419a51d0dd Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 10 Mar 2021 18:51:33 +0100 Subject: [PATCH 022/111] * Fix lint. --- test/python/pulse/test_frames.py | 50 +++++++++++++++++--------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 4f9904667afa..0211c5f68b4c 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -23,6 +23,7 @@ class TestFrame(QiskitTestCase): + """Basic frame tests.""" def basic(self): """Test basic functionality of frames.""" @@ -44,9 +45,9 @@ class TestResolvedFrames(QiskitTestCase): def setUp(self): super().setUp() - self.dt = 2.2222222222222221e-10 - self.f0 = 0.093e9 # Frequency of frame 0 - self.f1 = 0.125e9 # Frequency of frame 1 + self.dt_ = 2.2222222222222221e-10 + self.freq0 = 0.093e9 # Frequency of frame 0 + self.freq1 = 0.125e9 # Frequency of frame 1 def test_phase_advance(self): """Test that phases are properly set when frames are resolved.""" @@ -61,26 +62,27 @@ def test_phase_advance(self): pulse.play(sig0, d0) pulse.play(sig1, d0) - frames_config = frames_configuration([[d0], [d0]], [self.f0, self.f1], self.dt) + frames_config = frames_configuration([[d0], [d0]], [self.freq0, self.freq1], self.dt_) - frame0_ = ResolvedFrame(pulse.Frame(1), self.f0, 0.0, self.dt, []) - frame1_ = ResolvedFrame(pulse.Frame(2), self.f1, 0.0, self.dt, []) + frame0_ = ResolvedFrame(pulse.Frame(1), self.freq0, 0.0, self.dt_, []) + frame1_ = ResolvedFrame(pulse.Frame(2), self.freq1, 0.0, self.dt_, []) # Check that the resolved frames are tracking phases properly for time in [0, 160, 320, 480]: - phase = np.angle(np.exp(2.0j*np.pi*self.f0*time*self.dt)) % (2*np.pi) + phase = np.angle(np.exp(2.0j * np.pi * self.freq0 * time * self.dt_)) % (2 * np.pi) self.assertAlmostEqual(frame0_.phase(time), phase, places=8) - phase = np.angle(np.exp(2.0j * np.pi * self.f1 * time * self.dt)) % (2 * np.pi) + phase = np.angle(np.exp(2.0j * np.pi * self.freq1 * time * self.dt_)) % (2 * np.pi) self.assertAlmostEqual(frame1_.phase(time), phase, places=8) # Check that the proper phase instructions are added to the frame resolved schedules. resolved = resolve_frames(sched, frames_config).instructions - params = [(0, self.f0, 1), (160, self.f1, 4), (320, self.f0, 7), (480, self.f1, 10)] + params = [(0, self.freq0, 1), (160, self.freq1, 4), + (320, self.freq0, 7), (480, self.freq1, 10)] for time, frame_frequency, index in params: - phase = np.angle(np.exp(2.0j*np.pi*frame_frequency*time*self.dt)) % (2*np.pi) + phase = np.angle(np.exp(2.0j * np.pi * frame_frequency * time * self.dt_)) % (2 * np.pi) self.assertEqual(resolved[index][0], time) self.assertAlmostEqual(resolved[index][1].phase, phase, places=8) @@ -93,34 +95,34 @@ def test_phase_advance_with_instructions(self): pulse.play(sig, pulse.DriveChannel(0)) pulse.shift_phase(1.0, pulse.Frame(0)) - frame = ResolvedFrame(pulse.Frame(0), self.f0, 0.0, self.dt, []) + frame = ResolvedFrame(pulse.Frame(0), self.freq0, 0.0, self.dt_, []) frame.set_frame_instructions(sched) self.assertAlmostEqual(frame.phase(0), 0.0, places=8) # Test the phase right before the shift phase instruction - phase = np.angle(np.exp(2.0j*np.pi*self.f0*159*self.dt)) % (2*np.pi) + phase = np.angle(np.exp(2.0j * np.pi * self.freq0 * 159 * self.dt_)) % (2 * np.pi) self.assertAlmostEqual(frame.phase(159), phase, places=8) # Test the phase at and after the shift phase instruction - phase = (np.angle(np.exp(2.0j * np.pi * self.f0 * 160 * self.dt)) + 1.0) % (2 * np.pi) + phase = (np.angle(np.exp(2.0j * np.pi * self.freq0 * 160 * self.dt_)) + 1.0) % (2 * np.pi) self.assertAlmostEqual(frame.phase(160), phase, places=8) - phase = (np.angle(np.exp(2.0j * np.pi * self.f0 * 161 * self.dt)) + 1.0) % (2 * np.pi) + phase = (np.angle(np.exp(2.0j * np.pi * self.freq0 * 161 * self.dt_)) + 1.0) % (2 * np.pi) self.assertAlmostEqual(frame.phase(161), phase, places=8) def test_set_frequency(self): """Test setting the frequency of the resolved frame.""" - frame = ResolvedFrame(pulse.Frame(0), self.f1, 0.0, self.dt, []) - frame.set_frequency(16, self.f0) - frame.set_frequency(10, self.f1) - frame.set_frequency(10, self.f1) + frame = ResolvedFrame(pulse.Frame(0), self.freq1, 0.0, self.dt_, []) + frame.set_frequency(16, self.freq0) + frame.set_frequency(10, self.freq1) + frame.set_frequency(10, self.freq1) - self.assertAlmostEqual(frame.frequency(0), self.f1, places=8) - self.assertAlmostEqual(frame.frequency(10), self.f1, places=8) - self.assertAlmostEqual(frame.frequency(15), self.f1, places=8) - self.assertAlmostEqual(frame.frequency(16), self.f0, places=8) + self.assertAlmostEqual(frame.frequency(0), self.freq1, places=8) + self.assertAlmostEqual(frame.frequency(10), self.freq1, places=8) + self.assertAlmostEqual(frame.frequency(15), self.freq1, places=8) + self.assertAlmostEqual(frame.frequency(16), self.freq0, places=8) def test_broadcasting(self): """Test that resolved frames broadcast to control channels.""" @@ -136,11 +138,11 @@ def test_broadcasting(self): pulse.play(sig, pulse.ControlChannel(0)) frames_config = frames_configuration([[pulse.DriveChannel(3), pulse.ControlChannel(0)]], - [self.f0], self.dt) + [self.freq0], self.dt_) resolved = resolve_frames(sched, frames_config).instructions - phase = (np.angle(np.exp(2.0j * np.pi * self.f0 * 160 * self.dt)) + 1.23) % (2 * np.pi) + phase = (np.angle(np.exp(2.0j * np.pi * self.freq0 * 160 * self.dt_)) + 1.23) % (2 * np.pi) self.assertAlmostEqual(resolved[1][1].phase, 0.0, places=8) self.assertAlmostEqual(resolved[4][1].phase, phase, places=8) From 260fcbfa3603646dd66b0b240d27db276524b091 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 10 Mar 2021 18:56:30 +0100 Subject: [PATCH 023/111] * Fix cyclic import. --- qiskit/pulse/library/signal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index 7f6e046c616d..205d335d3cf1 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -15,7 +15,7 @@ from typing import Any, Dict, Optional, Union from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType -from qiskit.pulse.library import Pulse +from qiskit.pulse.library.pulse import Pulse from qiskit.pulse.frame import Frame from qiskit.pulse.exceptions import PulseError From d452e27f9143b5e9077bac19c513e3fe7542de13 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 10 Mar 2021 22:07:42 +0100 Subject: [PATCH 024/111] * Lint fix. --- qiskit/pulse/builder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index d28098243d1c..0319df99afa2 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -893,6 +893,7 @@ def wrapped_transform(*args, **kwargs): return wrap +# pylint: disable=unused-argument @_transform_context(transforms.align_left) def align_left(ignore_frames: bool = False) -> ContextManager[None]: """Left alignment pulse scheduling context. @@ -925,6 +926,7 @@ def align_left(ignore_frames: bool = False) -> ContextManager[None]: """ +# pylint: disable=unused-argument @_transform_context(transforms.align_right) def align_right(ignore_frames: bool = False) -> ContextManager[None]: """Right alignment pulse scheduling context. From 2aa651c77d9b018f1f701588c102cd187aff77e7 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 5 Apr 2021 16:49:31 +0200 Subject: [PATCH 025/111] * Fixed imports and refactored chan to the right location. --- qiskit/pulse/transforms/frames.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 07e5ff395407..15061748a933 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -13,13 +13,14 @@ """Basic rescheduling functions which take schedule or instructions and return new schedules.""" -from typing import Dict, List, Optional, Iterable, Union +from typing import Dict from qiskit.pulse.schedule import Schedule from qiskit.pulse.resolved_frame import ResolvedFrame, ChannelTracker from qiskit.pulse.library import Signal from qiskit.pulse.exceptions import PulseError from qiskit.pulse import channels as chans, instructions +from qiskit.pulse.frame import Frame @@ -70,9 +71,9 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu sched = Schedule(name=schedule.name, metadata=schedule.metadata) for time, inst in schedule.instructions: - chan = inst.channel - if isinstance(inst, instructions.Play): + chan = inst.channel + if isinstance(inst.operands[0], Signal): frame_idx = inst.operands[0].frame.index @@ -115,10 +116,14 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu # Insert phase and frequency commands that are not applied to frames. elif isinstance(inst, (instructions.SetFrequency, instructions.ShiftFrequency)): + chan = inst.channel + if issubclass(type(chan), chans.PulseChannel): sched.insert(time, type(inst)(inst.frequency, chan), inplace=True) elif isinstance(inst, (instructions.SetPhase, instructions.ShiftPhase)): + chan = inst.channel + if issubclass(type(chan), chans.PulseChannel): sched.insert(time, type(inst)(inst.phase, chan), inplace=True) From f1bfede88b2ecc1fb6e8e895779948d8e2bb5a62 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 5 Apr 2021 16:50:51 +0200 Subject: [PATCH 026/111] * Added property to Signal.parameters --- qiskit/pulse/library/signal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index 205d335d3cf1..af09e1bfe105 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -67,6 +67,7 @@ def is_parameterized(self) -> bool: """Determine if there are any parameters in the Signal.""" return self._pulse.is_parameterized() or self._frame.is_parameterized() + @property def parameters(self) -> Dict[str, Any]: """Return a list of parameters in the Signal.""" parameters = self._pulse.parameters From 5fab67e8b3cff850f5cc05997ad06551ec137217 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 5 Apr 2021 16:51:34 +0200 Subject: [PATCH 027/111] * Made the handeling of parameters in ResolvedFrame consistent with the new methodology. --- qiskit/pulse/resolved_frame.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/pulse/resolved_frame.py b/qiskit/pulse/resolved_frame.py index 6fbc2a8167dd..5c10098ac00a 100644 --- a/qiskit/pulse/resolved_frame.py +++ b/qiskit/pulse/resolved_frame.py @@ -149,7 +149,8 @@ def __init__(self, frame: Frame, frequency: float, phase: float, self._channels = channels for ch in self._channels: - self._parameters.update(ch.parameters) + if isinstance(ch.index, ParameterExpression): + self._parameters.update((ch.index, )) @property def channels(self) -> List[Channel]: From 1d34642c6f7daec67bdb196debed129882eca3fe Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 5 Apr 2021 16:52:56 +0200 Subject: [PATCH 028/111] * Added ignore_frames back to alignments as it was lost during merging of master. --- qiskit/pulse/transforms/__init__.py | 4 ++ qiskit/pulse/transforms/alignments.py | 55 ++++++++++++++++++++++----- qiskit/pulse/transforms/frames.py | 2 +- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/qiskit/pulse/transforms/__init__.py b/qiskit/pulse/transforms/__init__.py index 1189bc31953a..ae409d7e6009 100644 --- a/qiskit/pulse/transforms/__init__.py +++ b/qiskit/pulse/transforms/__init__.py @@ -94,3 +94,7 @@ from qiskit.pulse.transforms.dag import ( block_to_dag ) + +from qiskit.pulse.transforms.frames import ( + resolve_frames +) diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py index b9787df1e857..1999e9d31f5a 100644 --- a/qiskit/pulse/transforms/alignments.py +++ b/qiskit/pulse/transforms/alignments.py @@ -21,6 +21,7 @@ from qiskit.pulse.exceptions import PulseError from qiskit.pulse.schedule import Schedule, ScheduleComponent from qiskit.pulse.utils import instruction_duration_validation +from qiskit.pulse.frame import Frame class AlignmentKind(abc.ABC): @@ -69,7 +70,7 @@ class AlignLeft(AlignmentKind): """ is_sequential = False - def align(self, schedule: Schedule) -> Schedule: + def align(self, schedule: Schedule, ignore_frames: bool = False) -> Schedule: """Reallocate instructions according to the policy. Only top-level sub-schedules are aligned. If sub-schedules are nested, @@ -77,23 +78,30 @@ def align(self, schedule: Schedule) -> Schedule: Args: schedule: Schedule to align. + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. Returns: Schedule with reallocated instructions. """ aligned = Schedule() for _, child in schedule._children: - self._push_left_append(aligned, child) + self._push_left_append(aligned, child, ignore_frames) return aligned @staticmethod - def _push_left_append(this: Schedule, other: ScheduleComponent) -> Schedule: + def _push_left_append(this: Schedule, other: ScheduleComponent, + ignore_frames: bool) -> Schedule: """Return ``this`` with ``other`` inserted at the maximum time over all channels shared between ```this`` and ``other``. Args: this: Input schedule to which ``other`` will be inserted. other: Other schedule to insert. + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. Returns: Push left appended schedule. @@ -101,6 +109,14 @@ def _push_left_append(this: Schedule, other: ScheduleComponent) -> Schedule: this_channels = set(this.channels) other_channels = set(other.channels) shared_channels = list(this_channels & other_channels) + + # Conservatively assume that a Frame instruction could impact all channels + if not ignore_frames: + for ch in this_channels | other_channels: + if isinstance(ch, Frame): + shared_channels = list(this_channels | other_channels) + break + ch_slacks = [this.stop_time - this.ch_stop_time(channel) + other.ch_start_time(channel) for channel in shared_channels] @@ -125,7 +141,7 @@ class AlignRight(AlignmentKind): """ is_sequential = False - def align(self, schedule: Schedule) -> Schedule: + def align(self, schedule: Schedule, ignore_frames: bool = False) -> Schedule: """Reallocate instructions according to the policy. Only top-level sub-schedules are aligned. If sub-schedules are nested, @@ -133,17 +149,22 @@ def align(self, schedule: Schedule) -> Schedule: Args: schedule: Schedule to align. + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. Returns: Schedule with reallocated instructions. """ aligned = Schedule() for _, child in reversed(schedule._children): - aligned = self._push_right_prepend(aligned, child) + aligned = self._push_right_prepend(aligned, child, ignore_frames) return aligned @staticmethod - def _push_right_prepend(this: ScheduleComponent, other: ScheduleComponent) -> Schedule: + def _push_right_prepend(this: ScheduleComponent, + other: ScheduleComponent, + ignore_frames: bool) -> Schedule: """Return ``this`` with ``other`` inserted at the latest possible time such that ``other`` ends before it overlaps with any of ``this``. @@ -160,6 +181,14 @@ def _push_right_prepend(this: ScheduleComponent, other: ScheduleComponent) -> Sc this_channels = set(this.channels) other_channels = set(other.channels) shared_channels = list(this_channels & other_channels) + + # Conservatively assume that a Frame instruction could impact all channels + if not ignore_frames: + for ch in this_channels | other_channels: + if isinstance(ch, Frame): + shared_channels = list(this_channels | other_channels) + break + ch_slacks = [this.ch_start_time(channel) - other.ch_stop_time(channel) for channel in shared_channels] @@ -396,32 +425,38 @@ def pad(schedule: Schedule, return schedule -def align_left(schedule: Schedule) -> Schedule: +def align_left(schedule: Schedule, ignore_frames: bool = False) -> Schedule: """Align a list of pulse instructions on the left. Args: schedule: Input schedule of which top-level sub-schedules will be rescheduled. + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. Returns: New schedule with input `schedule`` child schedules and instructions left aligned. """ context = AlignLeft() - return context.align(schedule) + return context.align(schedule, ignore_frames) -def align_right(schedule: Schedule) -> Schedule: +def align_right(schedule: Schedule, ignore_frames: bool = False) -> Schedule: """Align a list of pulse instructions on the right. Args: schedule: Input schedule of which top-level sub-schedules will be rescheduled. + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. Returns: New schedule with input `schedule`` child schedules and instructions right aligned. """ context = AlignRight() - return context.align(schedule) + return context.align(schedule, ignore_frames) def align_sequential(schedule: Schedule) -> Schedule: diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 15061748a933..fe24e1b5b0ab 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -123,7 +123,7 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu elif isinstance(inst, (instructions.SetPhase, instructions.ShiftPhase)): chan = inst.channel - + if issubclass(type(chan), chans.PulseChannel): sched.insert(time, type(inst)(inst.phase, chan), inplace=True) From 2c9e834ef9bc7cf63024cd09c850befa8d99ff2c Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 5 Apr 2021 17:39:55 +0200 Subject: [PATCH 029/111] * Aligned Signal and Frame to the ParameterManager functionality of pulse. * Added corresponding unittest. --- qiskit/pulse/frame.py | 28 ++--------------------- qiskit/pulse/library/signal.py | 22 +----------------- qiskit/pulse/parameter_manager.py | 34 +++++++++++++++++++++++++++- test/python/pulse/test_parameters.py | 26 +++++++++++++++++++++ 4 files changed, 62 insertions(+), 48 deletions(-) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 4e361712adcc..24df33d8fe3f 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -16,7 +16,7 @@ import numpy as np from qiskit.circuit import Parameter -from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType +from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.pulse.exceptions import PulseError @@ -38,7 +38,7 @@ def __init__(self, index: Union[int, Parameter]): self._parameters.update(index.parameters) @property - def index(self) -> int: + def index(self) -> Union[int, ParameterExpression]: """Return the index of this frame. The index is a label for a frame.""" return self._index @@ -76,30 +76,6 @@ def is_parameterized(self) -> bool: """Return True iff the frame is parameterized.""" return bool(self.parameters) - def assign(self, parameter: Parameter, value: ParameterValueType) -> 'Frame': - """Return a new frame with the input Parameter assigned to value. - - Args: - parameter: A parameter in this expression whose value will be updated. - value: The new value to bind to. - - Returns: - Frame: A new frame with updated parameters. - - Raises: - PulseError: If the parameter is not present in the frame. - """ - if parameter not in self.parameters: - raise PulseError('Cannot bind parameters ({}) not present in the frame.' - ''.format(parameter)) - - new_index = self.index.assign(parameter, value) - if not new_index.parameters: - self._validate_index(new_index) - new_index = int(new_index) - - return type(self)(new_index) - def __repr__(self): return f'{self.__class__.__name__}({self._index})' diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index af09e1bfe105..eec65a9abf67 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -14,7 +14,7 @@ from typing import Any, Dict, Optional, Union -from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType +from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.pulse.library.pulse import Pulse from qiskit.pulse.frame import Frame from qiskit.pulse.exceptions import PulseError @@ -77,26 +77,6 @@ def parameters(self) -> Dict[str, Any]: return parameters - def assign_parameters(self, - value_dict: Dict[ParameterExpression, ParameterValueType]) -> 'Signal': - """ - Return a new Signal with parameters assigned. - - Args: - value_dict: A mapping from Parameters to either numeric values or another - Parameter expression. - - Returns: - Signal: a new signal with updated parameters. - """ - pulse = self._pulse.assign_parameters(value_dict) - frame = self._frame - for param, value in value_dict.items(): - if param in self._frame.parameters: - frame = self._frame.assign(param, value) - - return type(self)(pulse, frame) - def __eq__(self, other: 'Signal') -> bool: return self._pulse == other._pulse and self.frame == other.frame diff --git a/qiskit/pulse/parameter_manager.py b/qiskit/pulse/parameter_manager.py index 3983e41e248d..a3d7e6db769d 100644 --- a/qiskit/pulse/parameter_manager.py +++ b/qiskit/pulse/parameter_manager.py @@ -57,7 +57,8 @@ from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType from qiskit.pulse import instructions, channels from qiskit.pulse.exceptions import PulseError -from qiskit.pulse.library import ParametricPulse, Waveform +from qiskit.pulse.library import ParametricPulse, Waveform, Signal +from qiskit.pulse.frame import Frame from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.pulse.transforms.alignments import AlignmentKind from qiskit.pulse.utils import format_parameter_value @@ -205,6 +206,27 @@ def visit_Channel(self, node: channels.Channel): return node + def visit_Frame(self, node: Frame): + """Assign parameters to ``Frame`` object.""" + if node.is_parameterized(): + new_index = self._assign_parameter_expression(node.index) + if not isinstance(new_index, int) and new_index < 0: + raise PulseError('Frame index must be a nonnegative integer') + + return node.__class__(index=new_index) + + return node + + def visit_Signal(self, node: Signal): + """Assign parameters to ``Signal`` object.""" + if node.is_parameterized(): + frame = self.visit(node.frame) + pulse = self.visit(node.pulse) + + return node.__class__(pulse, frame, node.name) + + return node + def visit_ParametricPulse(self, node: ParametricPulse): """Assign parameters to ``ParametricPulse`` object.""" if node.is_parameterized(): @@ -316,6 +338,16 @@ def visit_Channel(self, node: channels.Channel): if isinstance(node.index, ParameterExpression): self._add_parameters(node.index) + def visit_Signal(self, node: Signal): + """Get parameters from ``Signal`` object.""" + self.visit(node.frame) + self.visit(node.pulse) + + def visit_Frame(self, node: Frame): + """Get parameters from ``Frame`` object.""" + if isinstance(node.index, ParameterExpression): + self._add_parameters(node.index) + def visit_ParametricPulse(self, node: ParametricPulse): """Get parameters from ``ParametricPulse`` object.""" for op_value in node.parameters.values(): diff --git a/test/python/pulse/test_parameters.py b/test/python/pulse/test_parameters.py index f222dc2a3612..830aa6cc87b9 100644 --- a/test/python/pulse/test_parameters.py +++ b/test/python/pulse/test_parameters.py @@ -20,6 +20,8 @@ from qiskit.circuit import Parameter from qiskit.pulse import PulseError from qiskit.pulse.channels import DriveChannel, AcquireChannel, MemorySlot +from qiskit.pulse.library import Signal +from qiskit.pulse.frame import Frame from qiskit.pulse.transforms import inline_subroutines from qiskit.test import QiskitTestCase from qiskit.test.mock import FakeAlmaden @@ -404,3 +406,27 @@ def test_cannot_build_schedule(self): sched = pulse.Schedule() with self.assertRaises(pulse.exceptions.UnassignedDurationError): sched.insert(0, test_play) + + +class TestParameterFrames(QiskitTestCase): + """Test that parameters and frames work.""" + + def test_frames(self): + """Test that we can create a parameterized schedule with Frames.""" + ch_param = Parameter('dx') + f_param = Parameter('fx') + dur = Parameter('duration') + phase = Parameter('phase') + + signal = Signal(pulse.Gaussian(dur, 0.1, 40), Frame(f_param)) + sched = pulse.ScheduleBlock() + sched.append(pulse.Play(signal, DriveChannel(ch_param))) + sched.append(pulse.ShiftPhase(phase, Frame(f_param))) + sched.append(pulse.Play(signal, DriveChannel(ch_param))) + + for param in [ch_param, f_param, dur, phase]: + self.assertTrue(param in sched.parameters) + + sched.assign_parameters({f_param: 3, ch_param: 2, dur: 160, phase: 1.57}) + + self.assertEqual(sched.parameters, set()) From 9843f88d648a0bf4af2192be160e7e2f6ba2d1a1 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 5 Apr 2021 17:51:19 +0200 Subject: [PATCH 030/111] * Fixed lint. --- qiskit/pulse/transforms/alignments.py | 61 +++++++++++++++++---------- qiskit/pulse/transforms/frames.py | 2 +- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py index 1999e9d31f5a..ef0d30bc3a53 100644 --- a/qiskit/pulse/transforms/alignments.py +++ b/qiskit/pulse/transforms/alignments.py @@ -70,7 +70,20 @@ class AlignLeft(AlignmentKind): """ is_sequential = False - def align(self, schedule: Schedule, ignore_frames: bool = False) -> Schedule: + def __init__(self, ignore_frames: bool = False): + """ + Left-alignment context. + + Args: + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. + """ + super().__init__() + + self._ignore_frames = ignore_frames + + def align(self, schedule: Schedule) -> Schedule: """Reallocate instructions according to the policy. Only top-level sub-schedules are aligned. If sub-schedules are nested, @@ -78,21 +91,16 @@ def align(self, schedule: Schedule, ignore_frames: bool = False) -> Schedule: Args: schedule: Schedule to align. - ignore_frames: If true then frame instructions will be ignore. This - should be set to true if the played Signals in this context - do not share any frames. Returns: Schedule with reallocated instructions. """ aligned = Schedule() for _, child in schedule._children: - self._push_left_append(aligned, child, ignore_frames) + self._push_left_append(aligned, child) return aligned - @staticmethod - def _push_left_append(this: Schedule, other: ScheduleComponent, - ignore_frames: bool) -> Schedule: + def _push_left_append(self, this: Schedule, other: ScheduleComponent) -> Schedule: """Return ``this`` with ``other`` inserted at the maximum time over all channels shared between ```this`` and ``other``. @@ -111,7 +119,7 @@ def _push_left_append(this: Schedule, other: ScheduleComponent, shared_channels = list(this_channels & other_channels) # Conservatively assume that a Frame instruction could impact all channels - if not ignore_frames: + if not self._ignore_frames: for ch in this_channels | other_channels: if isinstance(ch, Frame): shared_channels = list(this_channels | other_channels) @@ -141,7 +149,20 @@ class AlignRight(AlignmentKind): """ is_sequential = False - def align(self, schedule: Schedule, ignore_frames: bool = False) -> Schedule: + def __init__(self, ignore_frames: bool = False): + """ + Right-alignment context. + + Args: + ignore_frames: If true then frame instructions will be ignore. This + should be set to true if the played Signals in this context + do not share any frames. + """ + super().__init__() + + self._ignore_frames = ignore_frames + + def align(self, schedule: Schedule) -> Schedule: """Reallocate instructions according to the policy. Only top-level sub-schedules are aligned. If sub-schedules are nested, @@ -149,22 +170,16 @@ def align(self, schedule: Schedule, ignore_frames: bool = False) -> Schedule: Args: schedule: Schedule to align. - ignore_frames: If true then frame instructions will be ignore. This - should be set to true if the played Signals in this context - do not share any frames. Returns: Schedule with reallocated instructions. """ aligned = Schedule() for _, child in reversed(schedule._children): - aligned = self._push_right_prepend(aligned, child, ignore_frames) + aligned = self._push_right_prepend(aligned, child) return aligned - @staticmethod - def _push_right_prepend(this: ScheduleComponent, - other: ScheduleComponent, - ignore_frames: bool) -> Schedule: + def _push_right_prepend(self, this: ScheduleComponent, other: ScheduleComponent) -> Schedule: """Return ``this`` with ``other`` inserted at the latest possible time such that ``other`` ends before it overlaps with any of ``this``. @@ -183,7 +198,7 @@ def _push_right_prepend(this: ScheduleComponent, shared_channels = list(this_channels & other_channels) # Conservatively assume that a Frame instruction could impact all channels - if not ignore_frames: + if not self._ignore_frames: for ch in this_channels | other_channels: if isinstance(ch, Frame): shared_channels = list(this_channels | other_channels) @@ -438,8 +453,8 @@ def align_left(schedule: Schedule, ignore_frames: bool = False) -> Schedule: New schedule with input `schedule`` child schedules and instructions left aligned. """ - context = AlignLeft() - return context.align(schedule, ignore_frames) + context = AlignLeft(ignore_frames) + return context.align(schedule) def align_right(schedule: Schedule, ignore_frames: bool = False) -> Schedule: @@ -455,8 +470,8 @@ def align_right(schedule: Schedule, ignore_frames: bool = False) -> Schedule: New schedule with input `schedule`` child schedules and instructions right aligned. """ - context = AlignRight() - return context.align(schedule, ignore_frames) + context = AlignRight(ignore_frames) + return context.align(schedule) def align_sequential(schedule: Schedule) -> Schedule: diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index fe24e1b5b0ab..58eddb86fdb7 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -132,4 +132,4 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu instructions.Directive)): sched.insert(time, inst, inplace=True) - return sched \ No newline at end of file + return sched From 9410ca40da8b7ab56bbc27f675ff7602a4ca8955 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 5 Apr 2021 18:00:45 +0200 Subject: [PATCH 031/111] * Fixed test. --- test/python/pulse/test_frames.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 0211c5f68b4c..5eed9a7397d2 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -20,6 +20,7 @@ from qiskit.test import QiskitTestCase from qiskit.pulse.transforms import resolve_frames from qiskit.pulse.resolved_frame import ResolvedFrame +from qiskit.pulse.parameter_manager import ParameterManager class TestFrame(QiskitTestCase): @@ -34,10 +35,16 @@ def basic(self): def test_parameter(self): """Test parameter assignment.""" + parameter_manager = ParameterManager() param = Parameter('a') frame = pulse.Frame(param) self.assertTrue(frame.is_parameterized()) - self.assertEqual(frame.assign(param, 123), pulse.Frame(123)) + + parameter_manager.update_parameter_table(frame) + new_frame = parameter_manager.assign_parameters(frame, {param: 123}) + + self.assertEqual(new_frame, pulse.Frame(123)) + self.assertEqual(frame, pulse.Frame(param)) class TestResolvedFrames(QiskitTestCase): From 028e9df7ca4840a001b4326474fa74593eeac528 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 6 Apr 2021 10:17:32 +0200 Subject: [PATCH 032/111] * Added a warning to the builder. --- qiskit/pulse/builder.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index cb78fce96f73..867bc7181bba 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -1325,6 +1325,12 @@ def frequency_offset(frequency: float, yield finally: if compensate_phase: + # In some alignments such as sequential and functional, the absolute position of + # a pulse depends on the number of pulses added after this context. + # Thus this logic is not always correct. Use Frame instead for this. + warnings.warn('Phase compensation with `compensate_phase=True` is being deprecated. ' + 'Use Frame instead. See Qiskit-terra/#5977.', DeprecationWarning) + duration = builder.context_schedule.duration - t0 dt = active_backend().configuration().dt accumulated_phase = 2 * np.pi * ((duration * dt * frequency) % 1) From a38e029cccded59f1d0a9695e17d0dd9be7548a9 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 7 Apr 2021 17:46:04 +0200 Subject: [PATCH 033/111] * Removed is parameterized from Frame and self._parameters. --- qiskit/pulse/frame.py | 15 +-------------- qiskit/pulse/library/signal.py | 4 ++-- qiskit/pulse/parameter_manager.py | 2 +- qiskit/pulse/resolved_frame.py | 2 +- test/python/pulse/test_frames.py | 2 +- 5 files changed, 6 insertions(+), 19 deletions(-) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 24df33d8fe3f..fd4879fac805 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -12,7 +12,7 @@ """Implements a Frame.""" -from typing import Any, Set, Union +from typing import Any, Union import numpy as np from qiskit.circuit import Parameter @@ -33,10 +33,6 @@ def __init__(self, index: Union[int, Parameter]): self._index = index self._hash = None - self._parameters = set() - if isinstance(index, ParameterExpression): - self._parameters.update(index.parameters) - @property def index(self) -> Union[int, ParameterExpression]: """Return the index of this frame. The index is a label for a frame.""" @@ -67,15 +63,6 @@ def name(self) -> str: """Return the shorthand alias for this frame, which is based on its type and index.""" return '{}{}'.format(self.__class__.prefix, self._index) - @property - def parameters(self) -> Set: - """Parameters which determine the frame index.""" - return self._parameters - - def is_parameterized(self) -> bool: - """Return True iff the frame is parameterized.""" - return bool(self.parameters) - def __repr__(self): return f'{self.__class__.__name__}({self._index})' diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index eec65a9abf67..78387a2ad234 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -65,14 +65,14 @@ def duration(self) -> Union[int, ParameterExpression]: def is_parameterized(self) -> bool: """Determine if there are any parameters in the Signal.""" - return self._pulse.is_parameterized() or self._frame.is_parameterized() + return self._pulse.is_parameterized() or isinstance(self._frame.index, ParameterExpression) @property def parameters(self) -> Dict[str, Any]: """Return a list of parameters in the Signal.""" parameters = self._pulse.parameters - if self._frame.is_parameterized(): + if isinstance(self._frame.index, ParameterExpression): parameters['index'] = self._frame.index return parameters diff --git a/qiskit/pulse/parameter_manager.py b/qiskit/pulse/parameter_manager.py index a3d7e6db769d..5542ccbdcd75 100644 --- a/qiskit/pulse/parameter_manager.py +++ b/qiskit/pulse/parameter_manager.py @@ -208,7 +208,7 @@ def visit_Channel(self, node: channels.Channel): def visit_Frame(self, node: Frame): """Assign parameters to ``Frame`` object.""" - if node.is_parameterized(): + if isinstance(node.index, ParameterExpression): new_index = self._assign_parameter_expression(node.index) if not isinstance(new_index, int) and new_index < 0: raise PulseError('Frame index must be a nonnegative integer') diff --git a/qiskit/pulse/resolved_frame.py b/qiskit/pulse/resolved_frame.py index 5c10098ac00a..4278e8973c00 100644 --- a/qiskit/pulse/resolved_frame.py +++ b/qiskit/pulse/resolved_frame.py @@ -141,7 +141,7 @@ def __init__(self, frame: Frame, frequency: float, phase: float, Raises: PulseError: If there are still parameters in the given frame. """ - if frame.is_parameterized(): + if isinstance(frame.index, ParameterExpression): raise PulseError('A parameterized frame cannot be given to ResolvedFrame.') super().__init__(frame.index, sample_duration) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 5eed9a7397d2..01d3349df285 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -38,7 +38,7 @@ def test_parameter(self): parameter_manager = ParameterManager() param = Parameter('a') frame = pulse.Frame(param) - self.assertTrue(frame.is_parameterized()) + self.assertTrue(isinstance(frame.index, Parameter)) parameter_manager.update_parameter_table(frame) new_frame = parameter_manager.assign_parameters(frame, {param: 123}) From 2d39fe127df31345210ebc3f653d5c07c110b38d Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 7 Apr 2021 17:48:55 +0200 Subject: [PATCH 034/111] * Moved hash generation in Frame to constructor. --- qiskit/pulse/frame.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index fd4879fac805..49cbe2e73189 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -31,7 +31,7 @@ def __init__(self, index: Union[int, Parameter]): """ self._validate_index(index) self._index = index - self._hash = None + self._hash = hash((type(self), self._index)) @property def index(self) -> Union[int, ParameterExpression]: @@ -79,6 +79,4 @@ def __eq__(self, other: 'Frame') -> bool: return type(self) is type(other) and self.index == other.index def __hash__(self): - if self._hash is None: - self._hash = hash((type(self), self._index)) return self._hash From cae1affb7b73cc2de53b61398c356e05943c40e1 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 7 Apr 2021 17:54:59 +0200 Subject: [PATCH 035/111] * Moved validate_index from channels and frame to utils. --- qiskit/pulse/channels.py | 26 +++----------------------- qiskit/pulse/frame.py | 27 +++------------------------ qiskit/pulse/utils.py | 22 +++++++++++++++++++++- 3 files changed, 27 insertions(+), 48 deletions(-) diff --git a/qiskit/pulse/channels.py b/qiskit/pulse/channels.py index fb5971fb7a25..e37b42b1924c 100644 --- a/qiskit/pulse/channels.py +++ b/qiskit/pulse/channels.py @@ -21,14 +21,12 @@ assembler. """ from abc import ABCMeta -from typing import Any, Set, Union - -import numpy as np +from typing import Set, Union from qiskit.circuit import Parameter from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType from qiskit.pulse.exceptions import PulseError -from qiskit.pulse.utils import deprecated_functionality +from qiskit.pulse.utils import deprecated_functionality, validate_index class Channel(metaclass=ABCMeta): @@ -67,7 +65,7 @@ def __init__(self, index: int): Args: index: Index of channel. """ - self._validate_index(index) + validate_index(index) self._index = index self._hash = hash((self.__class__.__name__, self._index)) @@ -83,24 +81,6 @@ def index(self) -> Union[int, ParameterExpression]: """ return self._index - def _validate_index(self, index: Any) -> None: - """Raise a PulseError if the channel index is invalid, namely, if it's not a positive - integer. - - Raises: - PulseError: If ``index`` is not a nonnegative integer. - """ - if isinstance(index, ParameterExpression) and index.parameters: - # Parameters are unbound - return - elif isinstance(index, ParameterExpression): - index = float(index) - if index.is_integer(): - index = int(index) - - if not isinstance(index, (int, np.integer)) and index < 0: - raise PulseError('Channel index must be a nonnegative integer') - @property @deprecated_functionality def parameters(self) -> Set: diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 49cbe2e73189..77d7306bbc59 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -12,12 +12,11 @@ """Implements a Frame.""" -from typing import Any, Union -import numpy as np +from typing import Union from qiskit.circuit import Parameter from qiskit.circuit.parameterexpression import ParameterExpression -from qiskit.pulse.exceptions import PulseError +from qiskit.pulse.utils import validate_index class Frame: @@ -29,7 +28,7 @@ def __init__(self, index: Union[int, Parameter]): Args: index: The index of the frame. """ - self._validate_index(index) + validate_index(index) self._index = index self._hash = hash((type(self), self._index)) @@ -38,26 +37,6 @@ def index(self) -> Union[int, ParameterExpression]: """Return the index of this frame. The index is a label for a frame.""" return self._index - @staticmethod - def _validate_index(index: Any) -> None: - """ - Raise a PulseError if the Frame index is invalid, namely, if it's not a positive - integer. - - Raises: - PulseError: If ``index`` is not a non-negative integer. - """ - if isinstance(index, ParameterExpression) and index.parameters: - # Parameters are unbound - return - elif isinstance(index, ParameterExpression): - index = float(index) - if index.is_integer(): - index = int(index) - - if not isinstance(index, (int, np.integer)) and index < 0: - raise PulseError('Frame index must be a nonnegative integer') - @property def name(self) -> str: """Return the shorthand alias for this frame, which is based on its type and index.""" diff --git a/qiskit/pulse/utils.py b/qiskit/pulse/utils.py index 731adc64683a..ce20acca0a08 100644 --- a/qiskit/pulse/utils.py +++ b/qiskit/pulse/utils.py @@ -13,12 +13,13 @@ """Module for common pulse programming utilities.""" import functools import warnings -from typing import List, Dict, Union +from typing import Any, List, Dict, Union import numpy as np from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.pulse.exceptions import UnassignedDurationError, QiskitError +from qiskit.pulse.exceptions import PulseError def format_meas_map(meas_map: List[List[int]]) -> Dict[int, List[int]]: @@ -110,3 +111,22 @@ def wrapper(*args, **kwargs): stacklevel=2) return func(*args, **kwargs) return wrapper + + +def validate_index(index: Any) -> None: + """Raise a PulseError if the channel index is invalid, namely, if it's not a positive + integer. + + Raises: + PulseError: If ``index`` is not a nonnegative integer. + """ + if isinstance(index, ParameterExpression) and index.parameters: + # Parameters are unbound + return + elif isinstance(index, ParameterExpression): + index = float(index) + if index.is_integer(): + index = int(index) + + if not isinstance(index, (int, np.integer)) and index < 0: + raise PulseError('Channel index must be a nonnegative integer') From a41148c8b8b27c6d33175a0cd2df44ec37ccc0a6 Mon Sep 17 00:00:00 2001 From: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Wed, 7 Apr 2021 17:57:26 +0200 Subject: [PATCH 036/111] Update qiskit/pulse/library/signal.py Co-authored-by: Naoki Kanazawa --- qiskit/pulse/library/signal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index 78387a2ad234..117646ac7697 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -41,7 +41,7 @@ def __init__(self, pulse: Pulse, frame: Frame, name: Optional[str] = None): self._pulse = pulse self._frame = frame - self.name = name + self.name = name or f"{pulse.name}_{frame.name}" @property def id(self) -> int: # pylint: disable=invalid-name From fdf6852ccfded932500d8b1f0182d37ccfae4fed Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 7 Apr 2021 18:33:21 +0200 Subject: [PATCH 037/111] * Fixed docstring. --- qiskit/pulse/library/signal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index 117646ac7697..6af974bec63b 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -30,7 +30,7 @@ def __init__(self, pulse: Pulse, frame: Frame, name: Optional[str] = None): """ Args: pulse: The envelope of the signal. - frame: A reference to a frame onto which the envelope is multiplied. + frame: A reference to a frame in which the pulse will be played. name: Name of the signal. Raises: From e4197141c9bd04fb5fcb264c92b9d97731184cd1 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 7 Apr 2021 18:56:08 +0200 Subject: [PATCH 038/111] * Removed id from Signal. * Fixed in issue wih validate_index in Channel. --- qiskit/pulse/channels.py | 2 +- qiskit/pulse/library/signal.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/qiskit/pulse/channels.py b/qiskit/pulse/channels.py index e37b42b1924c..c82a69590f43 100644 --- a/qiskit/pulse/channels.py +++ b/qiskit/pulse/channels.py @@ -111,7 +111,7 @@ def assign(self, parameter: Parameter, value: ParameterValueType) -> 'Channel': new_index = self.index.assign(parameter, value) if not new_index.parameters: - self._validate_index(new_index) + validate_index(new_index) new_index = int(new_index) return type(self)(new_index) diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index 6af974bec63b..b5c6bd61c143 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -43,11 +43,6 @@ def __init__(self, pulse: Pulse, frame: Frame, name: Optional[str] = None): self._frame = frame self.name = name or f"{pulse.name}_{frame.name}" - @property - def id(self) -> int: # pylint: disable=invalid-name - """Unique identifier for this signal.""" - return id(self) - @property def pulse(self) -> Pulse: """Return the envelope.""" From b392d1ee7746b3842b90bed60b9225e837c782a1 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 7 Apr 2021 18:58:58 +0200 Subject: [PATCH 039/111] * Used validate_index in parameter manager. --- qiskit/pulse/parameter_manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/pulse/parameter_manager.py b/qiskit/pulse/parameter_manager.py index 5542ccbdcd75..cb7e3dbce9c4 100644 --- a/qiskit/pulse/parameter_manager.py +++ b/qiskit/pulse/parameter_manager.py @@ -61,7 +61,7 @@ from qiskit.pulse.frame import Frame 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_index class NodeVisitor: @@ -210,8 +210,7 @@ def visit_Frame(self, node: Frame): """Assign parameters to ``Frame`` object.""" if isinstance(node.index, ParameterExpression): new_index = self._assign_parameter_expression(node.index) - if not isinstance(new_index, int) and new_index < 0: - raise PulseError('Frame index must be a nonnegative integer') + validate_index(new_index) return node.__class__(index=new_index) From 3e46388357a415906ca3f161dc342270d2d90d28 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 7 Apr 2021 19:01:45 +0200 Subject: [PATCH 040/111] * Improved validate_index usage in parameter manager. --- qiskit/pulse/parameter_manager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/pulse/parameter_manager.py b/qiskit/pulse/parameter_manager.py index cb7e3dbce9c4..f90b67a953b1 100644 --- a/qiskit/pulse/parameter_manager.py +++ b/qiskit/pulse/parameter_manager.py @@ -196,10 +196,8 @@ def visit_Channel(self, node: channels.Channel): if node.is_parameterized(): new_index = self._assign_parameter_expression(node.index) - # validate if not isinstance(new_index, ParameterExpression): - if not isinstance(new_index, int) or new_index < 0: - raise PulseError('Channel index must be a nonnegative integer') + validate_index(new_index) # return new instance to prevent accidentally override timeslots without evaluation return node.__class__(index=new_index) @@ -210,7 +208,9 @@ def visit_Frame(self, node: Frame): """Assign parameters to ``Frame`` object.""" if isinstance(node.index, ParameterExpression): new_index = self._assign_parameter_expression(node.index) - validate_index(new_index) + + if not isinstance(new_index, ParameterExpression): + validate_index(new_index) return node.__class__(index=new_index) From 7ece5ae8678e01f8b833050e8b747f333edb3398 Mon Sep 17 00:00:00 2001 From: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Wed, 7 Apr 2021 19:06:33 +0200 Subject: [PATCH 041/111] Update qiskit/pulse/transforms/frames.py Co-authored-by: Naoki Kanazawa --- qiskit/pulse/transforms/frames.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 58eddb86fdb7..dd532b2a46a8 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -127,7 +127,7 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu if issubclass(type(chan), chans.PulseChannel): sched.insert(time, type(inst)(inst.phase, chan), inplace=True) - if isinstance(inst, (instructions.Delay, instructions.Call, + if isinstance(inst, (instructions.Delay, instructions.Snapshot, instructions.Acquire, instructions.Directive)): sched.insert(time, inst, inplace=True) From 858e136563060b7d8d8fe15f0c7f6216932b3656 Mon Sep 17 00:00:00 2001 From: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Wed, 7 Apr 2021 19:07:47 +0200 Subject: [PATCH 042/111] Update qiskit/pulse/builder.py Co-authored-by: Lauren Capelluto --- qiskit/pulse/builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index 867bc7181bba..5c02976e9313 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -920,7 +920,7 @@ def align_left(ignore_frames: bool = False) -> ContextManager[None]: assert pulse_prog.ch_start_time(d0) == pulse_prog.ch_start_time(d1) Args: - ignore_frames: If true then frame instructions will be ignore. This + ignore_frames: If true then frame instructions will be ignored. This should be set to true if the played Signals in this context do not share any frames. """ From 7ec977d0df0976cd94fefa90c823fbc251559116 Mon Sep 17 00:00:00 2001 From: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Wed, 7 Apr 2021 19:08:36 +0200 Subject: [PATCH 043/111] Update qiskit/pulse/transforms/alignments.py Co-authored-by: Lauren Capelluto --- qiskit/pulse/transforms/alignments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py index ef0d30bc3a53..ffa04f82e192 100644 --- a/qiskit/pulse/transforms/alignments.py +++ b/qiskit/pulse/transforms/alignments.py @@ -75,7 +75,7 @@ def __init__(self, ignore_frames: bool = False): Left-alignment context. Args: - ignore_frames: If true then frame instructions will be ignore. This + ignore_frames: If true then frame instructions will be ignored. This should be set to true if the played Signals in this context do not share any frames. """ From b7bc92fb05ca41a0c4b08a9764c0880393144ba4 Mon Sep 17 00:00:00 2001 From: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Wed, 7 Apr 2021 19:09:03 +0200 Subject: [PATCH 044/111] Update qiskit/pulse/resolved_frame.py Co-authored-by: Lauren Capelluto --- qiskit/pulse/resolved_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/resolved_frame.py b/qiskit/pulse/resolved_frame.py index 4278e8973c00..a6164140f840 100644 --- a/qiskit/pulse/resolved_frame.py +++ b/qiskit/pulse/resolved_frame.py @@ -61,7 +61,7 @@ def frequency(self, time: int) -> float: time: The maximum time for which to get the frequency. Returns: - frequency: The frequency of the frame up until time. + frequency: The frequency of the frame right before time. """ frequency = self._frequencies_phases[0][1] for time_freq in self._frequencies_phases: From dfc69716a7f84c6bb756df6e0102bc5172d65ea0 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 08:55:36 +0200 Subject: [PATCH 045/111] * Moved resolved_frame.py to transforms. --- qiskit/pulse/transforms/frames.py | 2 +- qiskit/pulse/{ => transforms}/resolved_frame.py | 0 test/python/pulse/test_frames.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename qiskit/pulse/{ => transforms}/resolved_frame.py (100%) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 58eddb86fdb7..bcc521535d8f 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -16,7 +16,7 @@ from typing import Dict from qiskit.pulse.schedule import Schedule -from qiskit.pulse.resolved_frame import ResolvedFrame, ChannelTracker +from qiskit.pulse.transforms.resolved_frame import ResolvedFrame, ChannelTracker from qiskit.pulse.library import Signal from qiskit.pulse.exceptions import PulseError from qiskit.pulse import channels as chans, instructions diff --git a/qiskit/pulse/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py similarity index 100% rename from qiskit/pulse/resolved_frame.py rename to qiskit/pulse/transforms/resolved_frame.py diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 01d3349df285..bfcd11cbefd6 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -19,7 +19,7 @@ import qiskit.pulse as pulse from qiskit.test import QiskitTestCase from qiskit.pulse.transforms import resolve_frames -from qiskit.pulse.resolved_frame import ResolvedFrame +from qiskit.pulse.transforms.resolved_frame import ResolvedFrame from qiskit.pulse.parameter_manager import ParameterManager From b66e192164d560859652d744f4c76a3498112d93 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 10:00:09 +0200 Subject: [PATCH 046/111] * Removed parameters from ResolvedFrame. --- qiskit/pulse/transforms/resolved_frame.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index a6164140f840..6a03d6612f79 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -13,10 +13,9 @@ """Implements a Frame.""" from abc import ABC -from typing import List, Union +from typing import List import numpy as np -from qiskit.circuit import Parameter from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.pulse.channels import Channel, PulseChannel from qiskit.pulse.frame import Frame @@ -32,7 +31,7 @@ class Tracker(ABC): or a pulse channel in a given schedule. """ - def __init__(self, index: Union[int, Parameter], sample_duration: float): + def __init__(self, index: int, sample_duration: float): """ Args: index: The index of the Tracker. Corresponds to the index of a @@ -44,10 +43,6 @@ def __init__(self, index: Union[int, Parameter], sample_duration: float): self._instructions = {} self._sample_duration = sample_duration - self._parameters = set() - if isinstance(index, ParameterExpression): - self._parameters.update(index.parameters) - @property def index(self) -> int: """Return the index of the tracker.""" @@ -150,7 +145,7 @@ def __init__(self, frame: Frame, frequency: float, phase: float, for ch in self._channels: if isinstance(ch.index, ParameterExpression): - self._parameters.update((ch.index, )) + raise PulseError('ResolvedFrame does not allow parameterized channels.') @property def channels(self) -> List[Channel]: From b365e30feedfbb4c403e84deb754320471cde24a Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 11:58:24 +0200 Subject: [PATCH 047/111] * Renamed sample_duration to dt. --- qiskit/compiler/assembler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index 2c8b4069a41a..907ba684245f 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -478,7 +478,7 @@ def _expand_parameters(circuits, run_config): def frames_configuration(frame_channels: List[List[PulseChannel]], frame_frequencies: List[float], - sample_duration: float, + dt: float, frame_indices: List[int] = None) -> Union[dict, None]: """ Ties together the frames of the backend and the frequencies of the frames. @@ -487,7 +487,7 @@ def frames_configuration(frame_channels: List[List[PulseChannel]], frame_channels: A List of lists. Sublist i is a list of channel names that frame i will broadcast on. frame_frequencies: A list of starting frequencies for each frame. - sample_duration: time of a sample. + dt: time of a sample. frame_indices: The indices of the frames. If None is given these will be in ascending order starting from 0. @@ -515,7 +515,7 @@ def frames_configuration(frame_channels: List[List[PulseChannel]], 'phase': 0.0, 'frequency': frame_frequencies[idx], 'channels': channels, - 'sample_duration': sample_duration + 'sample_duration': dt } return frames_config From 46623d8c85e6adf4cf7945916334f54bbed7f93e Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 12:27:38 +0200 Subject: [PATCH 048/111] * Added numerical threshhold. --- qiskit/pulse/transforms/frames.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 5f04d16a7afb..cb03878cb292 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -92,11 +92,11 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu freq_diff = frame_freq - channel_trackers[chan].frequency(time) phase_diff = frame_phase - channel_trackers[chan].phase(time) - if freq_diff != 0.0: + if abs(freq_diff) > 1e-8: shift_freq = instructions.ShiftFrequency(freq_diff, chan) sched.insert(time, shift_freq, inplace=True) - if phase_diff != 0.0: + if abs(phase_diff) > 1e-8: sched.insert(time, instructions.ShiftPhase(phase_diff, chan), inplace=True) # If the channel's phase and frequency has not been set in the past From b604ee10b0630b5422e57c10d674b86ebd4c5a1b Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 12:31:59 +0200 Subject: [PATCH 049/111] * Added raise on unsupported instructions in frames resolution. --- qiskit/pulse/transforms/frames.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index cb03878cb292..0f30b07fe484 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -127,9 +127,12 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu if issubclass(type(chan), chans.PulseChannel): sched.insert(time, type(inst)(inst.phase, chan), inplace=True) - if isinstance(inst, (instructions.Delay, - instructions.Snapshot, instructions.Acquire, - instructions.Directive)): + elif isinstance(inst, (instructions.Delay, + instructions.Snapshot, instructions.Acquire, + instructions.Directive)): sched.insert(time, inst, inplace=True) + else: + raise PulseError(f"Unsupported {inst.__class__.__name__} in frame resolution.") + return sched From eae9f3e6157de1ea8f9e385f9fddeed1937755ae Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 12:34:47 +0200 Subject: [PATCH 050/111] * Improved docstring. --- qiskit/compiler/assembler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index 907ba684245f..c9ba2c7074f6 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -487,7 +487,7 @@ def frames_configuration(frame_channels: List[List[PulseChannel]], frame_channels: A List of lists. Sublist i is a list of channel names that frame i will broadcast on. frame_frequencies: A list of starting frequencies for each frame. - dt: time of a sample. + dt: duration of a sample in the waveforms. frame_indices: The indices of the frames. If None is given these will be in ascending order starting from 0. From cf82c2b288512bc4cbf983fed763ad64c870bea8 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 17:11:13 +0200 Subject: [PATCH 051/111] * Changed frames_config to have the frame as key. --- qiskit/assembler/assemble_schedules.py | 9 ++++++--- qiskit/compiler/assembler.py | 9 ++++----- qiskit/pulse/transforms/frames.py | 8 ++++---- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index e0b2dfd3db3d..6f989916592c 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -324,8 +324,11 @@ def _assemble_config(lo_converter: converters.LoConfigConverter, # frames config frames_config = qobj_config.get('frames_config', None) if frames_config: - for frame_config in frames_config.values(): - frame_config['channels'] = [ch.name for ch in frame_config['channels']] - frame_config['frame'] = frame_config['frame'].name + frames_config_ = {} + for frame, settings in frames_config.items(): + frames_config_[frame.name] = settings + frames_config_[frame.name]['channels'] = [ch.name for ch in settings['channels']] + + qobj_config['frames_config'] = frames_config_ return qobj.PulseQobjConfig(**qobj_config) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index c9ba2c7074f6..f8040acc99c2 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -317,10 +317,10 @@ def _parse_pulse_args(backend, qubit_lo_freq, meas_lo_freq, qubit_lo_range, if frames_config is None: frames_config = frames_config_ else: - for index, config in frames_config_.items(): + for frame, settings in frames_config_.items(): # Do not override the frames provided by the user. - if index not in frames_config: - frames_config[index] = config + if frame not in frames_config: + frames_config[frame] = settings dynamic_reprate_enabled = getattr(backend_config, 'dynamic_reprate_enabled', False) @@ -510,8 +510,7 @@ def frames_configuration(frame_channels: List[List[PulseChannel]], else: index = idx - frames_config[index] = { - 'frame': Frame(index), + frames_config[Frame(index)] = { 'phase': 0.0, 'frequency': frame_frequencies[idx], 'channels': channels, diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 0f30b07fe484..01f097a02e9d 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -24,7 +24,7 @@ -def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedule: +def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Schedule: """ Parse the schedule and replace instructions on Frames by instructions on the appropriate channels. @@ -47,11 +47,11 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[int, Dict]) -> Schedu resolved_frames = {} sample_duration = None - for frame_settings in frames_config.values(): - frame = ResolvedFrame(**frame_settings) + for frame, settings in frames_config.items(): + frame = ResolvedFrame(frame, **settings) frame.set_frame_instructions(schedule) resolved_frames[frame.index] = frame - sample_duration = frame_settings['sample_duration'] + sample_duration = settings['sample_duration'] if sample_duration is None: raise PulseError('Frame configuration does not have a sample duration.') From 46292e92088693355ec5e74e8d1714c3f8c50e15 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 17:33:34 +0200 Subject: [PATCH 052/111] * Cleaned up index notation. --- qiskit/compiler/assembler.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index f8040acc99c2..10fa5525d29f 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -504,15 +504,15 @@ def frames_configuration(frame_channels: List[List[PulseChannel]], f'the number of frame initial frequencies {len(frame_frequencies)}.') frames_config = {} - for idx, channels in enumerate(frame_channels): + for index, channels in enumerate(frame_channels): if frame_indices: - index = frame_indices[idx] + frame_index = frame_indices[index] else: - index = idx + frame_index = index - frames_config[Frame(index)] = { + frames_config[Frame(frame_index)] = { 'phase': 0.0, - 'frequency': frame_frequencies[idx], + 'frequency': frame_frequencies[index], 'channels': channels, 'sample_duration': dt } From 6332748ae17805b69c865a972eee20ffea23aec6 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 17:37:44 +0200 Subject: [PATCH 053/111] * Fixed docstring. --- qiskit/pulse/transforms/alignments.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py index ffa04f82e192..1bbe19619577 100644 --- a/qiskit/pulse/transforms/alignments.py +++ b/qiskit/pulse/transforms/alignments.py @@ -107,9 +107,6 @@ def _push_left_append(self, this: Schedule, other: ScheduleComponent) -> Schedul Args: this: Input schedule to which ``other`` will be inserted. other: Other schedule to insert. - ignore_frames: If true then frame instructions will be ignore. This - should be set to true if the played Signals in this context - do not share any frames. Returns: Push left appended schedule. From 94a8cd76df32a11e911bdc0158db265dd0e4dccf Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 17:43:16 +0200 Subject: [PATCH 054/111] * Fixed comment. --- qiskit/pulse/transforms/resolved_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 6a03d6612f79..4ac432ea8639 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -39,7 +39,7 @@ def __init__(self, index: int, sample_duration: float): sample_duration: Duration of a sample. """ self._index = index - self._frequencies_phases = [] # Tuple of (time, frequency, phase) + self._frequencies_phases = [] # List of (time, frequency, phase) self._instructions = {} self._sample_duration = sample_duration From fdb1792d7be56e74697702841dd2c5c8ef878a4c Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 17:58:47 +0200 Subject: [PATCH 055/111] * Removed _frequency and _phase from ChannelTracker. * Cleaned up style. --- qiskit/pulse/transforms/resolved_frame.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 4ac432ea8639..fd30a37f3b91 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -79,9 +79,9 @@ def phase(self, time: int) -> float: """ if len(self._frequencies_phases) == 0: return 0.0 - else: - phase = self._frequencies_phases[0][1] - last_time = self._frequencies_phases[0][0] + + phase = self._frequencies_phases[0][1] + last_time = self._frequencies_phases[0][0] for time_freq in self._frequencies_phases: if time_freq[0] <= time: @@ -195,9 +195,7 @@ def __init__(self, channel: PulseChannel, sample_duration: float): """ super().__init__(channel.index, sample_duration) self._channel = channel - self._frequencies = [] - self._phases = [] def is_initialized(self) -> bool: """Return true if the channel has been initialized.""" - return len(self._frequencies) > 0 + return len(self._frequencies_phases) > 0 From 8f6d1fc212e282e7951a2799f960520a3eacff98 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 18:01:52 +0200 Subject: [PATCH 056/111] * Improved code efficient in resolved frame. --- qiskit/pulse/transforms/resolved_frame.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index fd30a37f3b91..51de77492da5 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -100,6 +100,8 @@ def set_frequency(self, time: int, frequency: float): for idx, time_freq in enumerate(self._frequencies_phases): if time_freq[0] < time: insert_idx = idx + else: + break phase = self.phase(time) @@ -111,6 +113,8 @@ def set_phase(self, time: int, phase: float): for idx, time_freq in enumerate(self._frequencies_phases): if time_freq[0] < time: insert_idx = idx + else: + break frequency = self.frequency(time) From a785b7c9792be4b038ba83073c2bce9a8f149553 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 20:12:30 +0200 Subject: [PATCH 057/111] * Cleaned-up transforms.frames.py. --- qiskit/pulse/transforms/frames.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 01f097a02e9d..f37648dfee63 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -10,8 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Basic rescheduling functions which take schedule or instructions and return new schedules.""" - +"""Replace a schedule with frames by one with instructions on PulseChannels only.""" from typing import Dict @@ -48,9 +47,11 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Sche resolved_frames = {} sample_duration = None for frame, settings in frames_config.items(): - frame = ResolvedFrame(frame, **settings) - frame.set_frame_instructions(schedule) - resolved_frames[frame.index] = frame + resolved_frame = ResolvedFrame(frame, **settings) + + # Extract shift and set frame operations from the schedule. + resolved_frame.set_frame_instructions(schedule) + resolved_frames[frame] = resolved_frame sample_duration = settings['sample_duration'] if sample_duration is None: @@ -63,8 +64,8 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Sche channel_trackers[ch] = ChannelTracker(ch, sample_duration) # Add the channels that the frames broadcast on. - for frame in resolved_frames.values(): - for ch in frame.channels: + for resolved_frame in resolved_frames.values(): + for ch in resolved_frame.channels: if ch not in channel_trackers: channel_trackers[ch] = ChannelTracker(ch, sample_duration) @@ -75,16 +76,16 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Sche chan = inst.channel if isinstance(inst.operands[0], Signal): - frame_idx = inst.operands[0].frame.index + frame = inst.operands[0].frame - if frame_idx not in resolved_frames: - raise PulseError(f'{Frame(frame.index)} is not configured and cannot ' + if frame not in resolved_frames: + raise PulseError(f'{frame} is not configured and cannot ' f'be resolved.') - frame = resolved_frames[frame_idx] + resolved_frame = resolved_frames[frame] - frame_freq = frame.frequency(time) - frame_phase = frame.phase(time) + frame_freq = resolved_frame.frequency(time) + frame_phase = resolved_frame.phase(time) # If the frequency and phase of the channel has already been set once in # The past we compute shifts. From a1b160d47437aff3e30dc0966a145dcbed8d8aa1 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 20:18:51 +0200 Subject: [PATCH 058/111] * Changed operands[0] -> pulse. --- qiskit/pulse/transforms/frames.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index f37648dfee63..51195eb07a6c 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -75,8 +75,8 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Sche if isinstance(inst, instructions.Play): chan = inst.channel - if isinstance(inst.operands[0], Signal): - frame = inst.operands[0].frame + if isinstance(inst.pulse, Signal): + frame = inst.pulse.frame if frame not in resolved_frames: raise PulseError(f'{frame} is not configured and cannot ' From b32ff0606979d2dd0031aa7294fe8851e35f298e Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 8 Apr 2021 21:02:35 +0200 Subject: [PATCH 059/111] * Improved documentation of tests. --- test/python/pulse/test_frames.py | 54 +++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index bfcd11cbefd6..82f050396bf1 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -85,13 +85,13 @@ def test_phase_advance(self): # Check that the proper phase instructions are added to the frame resolved schedules. resolved = resolve_frames(sched, frames_config).instructions - params = [(0, self.freq0, 1), (160, self.freq1, 4), - (320, self.freq0, 7), (480, self.freq1, 10)] + params = [(0, 0, self.freq0, 1), (160, 160, self.freq1 - self.freq0, 4), + (320, 160, self.freq0 - self.freq1, 7), (480, 160, self.freq1 - self.freq0, 10)] - for time, frame_frequency, index in params: - phase = np.angle(np.exp(2.0j * np.pi * frame_frequency * time * self.dt_)) % (2 * np.pi) + for time, delta, frame_frequency, index in params: + phase = np.angle(np.exp(2.0j * np.pi * frame_frequency * delta * self.dt_)) % (2 * np.pi) self.assertEqual(resolved[index][0], time) - self.assertAlmostEqual(resolved[index][1].phase, phase, places=8) + self.assertAlmostEqual(resolved[index][1].phase % (2*np.pi), phase, places=8) def test_phase_advance_with_instructions(self): """Test that the phase advances are properly computed with frame instructions.""" @@ -151,6 +151,44 @@ def test_broadcasting(self): phase = (np.angle(np.exp(2.0j * np.pi * self.freq0 * 160 * self.dt_)) + 1.23) % (2 * np.pi) - self.assertAlmostEqual(resolved[1][1].phase, 0.0, places=8) - self.assertAlmostEqual(resolved[4][1].phase, phase, places=8) - self.assertAlmostEqual(resolved[6][1].phase, phase, places=8) + # Check that the frame resolved schedule has the correct phase and frequency instructions + # at the right place. + # First, ensure that resolved starts with a SetFrequency and SetPhase. + self.assertEquals(resolved[0][0], 0) + set_freq = resolved[0][1] + if isinstance(set_freq, pulse.SetFrequency): + self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) + else: + self.fail() + + self.assertEquals(resolved[1][0], 0) + set_phase = resolved[1][1] + if isinstance(set_phase, pulse.SetPhase): + self.assertAlmostEqual(set_phase.phase, 0.0, places=8) + else: + self.fail() + + # Next, check that we do phase shifts on the DriveChannel after the first Gaussian. + self.assertEquals(resolved[3][0], 160) + shift_phase = resolved[3][1] + if isinstance(shift_phase, pulse.ShiftPhase): + self.assertAlmostEqual(shift_phase.phase, 1.23, places=8) + else: + self.fail() + + # Up to now, no pulse has been applied on the ControlChannel so we should + # encounter a Set instructions at time 160 which is when the first pulse + # is played on ControlChannel(0) + self.assertEquals(resolved[4][0], 160) + set_freq = resolved[4][1] + if isinstance(set_freq, pulse.SetFrequency): + self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) + else: + self.fail() + + self.assertEquals(resolved[5][0], 160) + set_phase = resolved[5][1] + if isinstance(set_phase, pulse.SetPhase): + self.assertAlmostEqual(set_phase.phase, phase, places=8) + else: + self.fail() From 8c710ac5f3b76246c9cd6dfb7069c26fc7356508 Mon Sep 17 00:00:00 2001 From: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Fri, 9 Apr 2021 12:02:00 +0200 Subject: [PATCH 060/111] Update qiskit/pulse/transforms/resolved_frame.py Co-authored-by: Lauren Capelluto --- qiskit/pulse/transforms/resolved_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 51de77492da5..b07b878f11be 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -39,7 +39,7 @@ def __init__(self, index: int, sample_duration: float): sample_duration: Duration of a sample. """ self._index = index - self._frequencies_phases = [] # List of (time, frequency, phase) + self._frequencies_phases = [] # List of (time, frequency, phase) tuples self._instructions = {} self._sample_duration = sample_duration From 399fce40e693df0cad736823a3c500d353960c23 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 9 Apr 2021 12:05:20 +0200 Subject: [PATCH 061/111] * Moved to assertTrue. --- test/python/pulse/test_frames.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 82f050396bf1..222ab5fea37d 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -156,39 +156,29 @@ def test_broadcasting(self): # First, ensure that resolved starts with a SetFrequency and SetPhase. self.assertEquals(resolved[0][0], 0) set_freq = resolved[0][1] - if isinstance(set_freq, pulse.SetFrequency): - self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) - else: - self.fail() - + self.assertTrue(isinstance(set_freq, pulse.SetFrequency)) + self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) + self.assertEquals(resolved[1][0], 0) set_phase = resolved[1][1] - if isinstance(set_phase, pulse.SetPhase): - self.assertAlmostEqual(set_phase.phase, 0.0, places=8) - else: - self.fail() + self.assertTrue(isinstance(set_phase, pulse.SetPhase)) + self.assertAlmostEqual(set_phase.phase, 0.0, places=8) # Next, check that we do phase shifts on the DriveChannel after the first Gaussian. self.assertEquals(resolved[3][0], 160) shift_phase = resolved[3][1] - if isinstance(shift_phase, pulse.ShiftPhase): - self.assertAlmostEqual(shift_phase.phase, 1.23, places=8) - else: - self.fail() + self.assertTrue(isinstance(shift_phase, pulse.ShiftPhase)) + self.assertAlmostEqual(shift_phase.phase, 1.23, places=8) # Up to now, no pulse has been applied on the ControlChannel so we should # encounter a Set instructions at time 160 which is when the first pulse # is played on ControlChannel(0) self.assertEquals(resolved[4][0], 160) set_freq = resolved[4][1] - if isinstance(set_freq, pulse.SetFrequency): - self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) - else: - self.fail() + self.assertTrue(isinstance(set_freq, pulse.SetFrequency)) + self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) self.assertEquals(resolved[5][0], 160) set_phase = resolved[5][1] - if isinstance(set_phase, pulse.SetPhase): - self.assertAlmostEqual(set_phase.phase, phase, places=8) - else: - self.fail() + self.assertTrue(isinstance(set_phase, pulse.SetPhase)) + self.assertAlmostEqual(set_phase.phase, phase, places=8) From 4674648faf48d405acac2b969dba2c2505fbf017 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 31 May 2021 16:55:26 +0200 Subject: [PATCH 062/111] * Fix issues from merging. --- qiskit/compiler/assembler.py | 82 +++--------------------------------- qiskit/pulse/channels.py | 4 +- 2 files changed, 8 insertions(+), 78 deletions(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index f41bbb12196d..f051e0579e37 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -27,17 +27,10 @@ from qiskit.pulse import LoConfig, Instruction from qiskit.pulse import Schedule, ScheduleBlock from qiskit.pulse.channels import PulseChannel +from qiskit.pulse.frame import Frame from qiskit.qobj import QobjHeader, Qobj from qiskit.qobj.utils import MeasLevel, MeasReturnType from qiskit.validation.jsonschema import SchemaValidationError -<<<<<<< HEAD -from qiskit.providers import BaseBackend -from qiskit.providers.backend import Backend -from qiskit.pulse.channels import PulseChannel -from qiskit.pulse import Schedule -from qiskit.pulse.frame import Frame -======= ->>>>>>> faf0628161fe20a3449c56cf30f56038b0116c32 logger = logging.getLogger(__name__) @@ -48,32 +41,6 @@ def _log_assembly_time(start_time, end_time): # TODO: parallelize over the experiments (serialize each separately, then add global header/config) -<<<<<<< HEAD -def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule, List[Schedule]], - backend: Optional[Union[Backend, BaseBackend]] = None, - qobj_id: Optional[str] = None, - qobj_header: Optional[Union[QobjHeader, Dict]] = None, - shots: Optional[int] = None, memory: Optional[bool] = False, - max_credits: Optional[int] = None, - seed_simulator: Optional[int] = None, - qubit_lo_freq: Optional[List[int]] = None, - meas_lo_freq: Optional[List[int]] = None, - qubit_lo_range: Optional[List[int]] = None, - meas_lo_range: Optional[List[int]] = None, - schedule_los: Optional[Union[List[Union[Dict[PulseChannel, float], LoConfig]], - Union[Dict[PulseChannel, float], LoConfig]]] = None, - meas_level: Union[int, MeasLevel] = MeasLevel.CLASSIFIED, - meas_return: Union[str, MeasReturnType] = MeasReturnType.AVERAGE, - meas_map: Optional[List[List[Qubit]]] = None, - memory_slot_size: int = 100, - rep_time: Optional[int] = None, - rep_delay: Optional[float] = None, - parameter_binds: Optional[List[Dict[Parameter, float]]] = None, - parametric_pulses: Optional[List[str]] = None, - init_qubits: bool = True, - frames_config: Dict[int, Dict] = None, - **run_config: Dict) -> Qobj: -======= def assemble( experiments: Union[ QuantumCircuit, @@ -109,9 +76,9 @@ def assemble( parameter_binds: Optional[List[Dict[Parameter, float]]] = None, parametric_pulses: Optional[List[str]] = None, init_qubits: bool = True, + frames_config: Dict[int, Dict] = None, **run_config: Dict, ) -> Qobj: ->>>>>>> faf0628161fe20a3449c56cf30f56038b0116c32 """Assemble a list of circuits or pulse schedules into a ``Qobj``. This function serializes the payloads, which could be either circuits or schedules, @@ -220,18 +187,6 @@ def assemble( ) end_time = time() _log_assembly_time(start_time, end_time) -<<<<<<< HEAD - return assemble_circuits(circuits=bound_experiments, qobj_id=qobj_id, - qobj_header=qobj_header, run_config=run_config) - - elif all(isinstance(exp, (Schedule, Instruction)) for exp in experiments): - run_config = _parse_pulse_args(backend, qubit_lo_freq, meas_lo_freq, - qubit_lo_range, meas_lo_range, - schedule_los, meas_level, meas_return, - meas_map, memory_slot_size, - rep_time, parametric_pulses, frames_config, - **run_config_common_dict) -======= return assemble_circuits( circuits=bound_experiments, qobj_id=qobj_id, @@ -253,9 +208,9 @@ def assemble( memory_slot_size, rep_time, parametric_pulses, + frames_config, **run_config_common_dict, ) ->>>>>>> faf0628161fe20a3449c56cf30f56038b0116c32 end_time = time() _log_assembly_time(start_time, end_time) @@ -366,14 +321,6 @@ def _parse_common_args( return qobj_id, qobj_header, run_config_dict -<<<<<<< HEAD -def _parse_pulse_args(backend, qubit_lo_freq, meas_lo_freq, qubit_lo_range, - meas_lo_range, schedule_los, meas_level, - meas_return, meas_map, - memory_slot_size, - rep_time, parametric_pulses, frames_config, - **run_config): -======= def _parse_pulse_args( backend, qubit_lo_freq, @@ -387,9 +334,9 @@ def _parse_pulse_args( memory_slot_size, rep_time, parametric_pulses, + frames_config, **run_config, ): ->>>>>>> faf0628161fe20a3449c56cf30f56038b0116c32 """Build a pulse RunConfig replacing unset arguments with defaults derived from the `backend`. See `assemble` for more information on the required arguments. @@ -433,7 +380,6 @@ def _parse_pulse_args( qubit_lo_range = qubit_lo_range or getattr(backend_config, "qubit_lo_range", None) meas_lo_range = meas_lo_range or getattr(backend_config, "meas_lo_range", None) -<<<<<<< HEAD frames_config_ = None if hasattr(backend_config, 'frames'): frames_config_ = frames_configuration(backend_config.frames(), @@ -448,10 +394,7 @@ def _parse_pulse_args( if frame not in frames_config: frames_config[frame] = settings - dynamic_reprate_enabled = getattr(backend_config, 'dynamic_reprate_enabled', False) -======= dynamic_reprate_enabled = getattr(backend_config, "dynamic_reprate_enabled", False) ->>>>>>> faf0628161fe20a3449c56cf30f56038b0116c32 rep_time = rep_time or getattr(backend_config, "rep_times", None) if rep_time: @@ -468,21 +411,6 @@ def _parse_pulse_args( parametric_pulses = parametric_pulses or getattr(backend_config, "parametric_pulses", []) # create run configuration and populate -<<<<<<< HEAD - run_config_dict = dict(qubit_lo_freq=qubit_lo_freq, - meas_lo_freq=meas_lo_freq, - qubit_lo_range=qubit_lo_range, - meas_lo_range=meas_lo_range, - schedule_los=schedule_los, - meas_level=meas_level, - meas_return=meas_return, - meas_map=meas_map, - memory_slot_size=memory_slot_size, - rep_time=rep_time, - parametric_pulses=parametric_pulses, - frames_config=frames_config, - **run_config) -======= run_config_dict = dict( qubit_lo_freq=qubit_lo_freq, meas_lo_freq=meas_lo_freq, @@ -495,9 +423,9 @@ def _parse_pulse_args( memory_slot_size=memory_slot_size, rep_time=rep_time, parametric_pulses=parametric_pulses, + frames_config=frames_config, **run_config, ) ->>>>>>> faf0628161fe20a3449c56cf30f56038b0116c32 run_config = RunConfig(**{k: v for k, v in run_config_dict.items() if v is not None}) return run_config diff --git a/qiskit/pulse/channels.py b/qiskit/pulse/channels.py index 8e7d8b3425a8..03fc6fe695c3 100644 --- a/qiskit/pulse/channels.py +++ b/qiskit/pulse/channels.py @@ -21,7 +21,9 @@ assembler. """ from abc import ABCMeta -from typing import Set, Union +from typing import Any, Set, Union + +import numpy as np from qiskit.circuit import Parameter from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType From 27dc741d0f7d17886d1d8e23b3b7132a7b05c88d Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 31 May 2021 17:11:24 +0200 Subject: [PATCH 063/111] * Black. --- qiskit/assembler/assemble_schedules.py | 13 ++++----- qiskit/compiler/assembler.py | 32 +++++++++++++---------- qiskit/pulse/builder.py | 6 ++--- qiskit/pulse/frame.py | 9 ++++--- qiskit/pulse/instructions/play.py | 10 ++++--- qiskit/pulse/library/signal.py | 6 ++--- qiskit/pulse/transforms/alignments.py | 11 +++++--- qiskit/pulse/transforms/frames.py | 20 ++++++++------ qiskit/pulse/transforms/resolved_frame.py | 22 ++++++++++------ qiskit/pulse/utils.py | 2 +- test/python/pulse/test_frames.py | 25 +++++++++++------- test/python/pulse/test_parameters.py | 8 +++--- 12 files changed, 97 insertions(+), 67 deletions(-) diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index 9a8430af41f4..a3e4a51d98a0 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -110,9 +110,10 @@ def _assemble_experiments( else: formatted_schedules.append(pulse.Schedule(sched)) - frames_config = getattr(run_config, 'frames_config', None) - resolved_schedules = [transforms.resolve_frames(sched, frames_config) - for sched in formatted_schedules] + frames_config = getattr(run_config, "frames_config", None) + resolved_schedules = [ + transforms.resolve_frames(sched, frames_config) for sched in formatted_schedules + ] compressed_schedules = transforms.compress_pulses(resolved_schedules) @@ -342,13 +343,13 @@ def _assemble_config( qobj_config["meas_lo_freq"] = [freq / 1e9 for freq in m_los] # frames config - frames_config = qobj_config.get('frames_config', None) + frames_config = qobj_config.get("frames_config", None) if frames_config: frames_config_ = {} for frame, settings in frames_config.items(): frames_config_[frame.name] = settings - frames_config_[frame.name]['channels'] = [ch.name for ch in settings['channels']] + frames_config_[frame.name]["channels"] = [ch.name for ch in settings["channels"]] - qobj_config['frames_config'] = frames_config_ + qobj_config["frames_config"] = frames_config_ return qobj.PulseQobjConfig(**qobj_config) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index f051e0579e37..82031e4df843 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -381,10 +381,10 @@ def _parse_pulse_args( meas_lo_range = meas_lo_range or getattr(backend_config, "meas_lo_range", None) frames_config_ = None - if hasattr(backend_config, 'frames'): - frames_config_ = frames_configuration(backend_config.frames(), - qubit_lo_freq, - backend_config.dt) + if hasattr(backend_config, "frames"): + frames_config_ = frames_configuration( + backend_config.frames(), qubit_lo_freq, backend_config.dt + ) if frames_config is None: frames_config = frames_config_ @@ -560,10 +560,12 @@ def _expand_parameters(circuits, run_config): return circuits, run_config -def frames_configuration(frame_channels: List[List[PulseChannel]], - frame_frequencies: List[float], - dt: float, - frame_indices: List[int] = None) -> Union[dict, None]: +def frames_configuration( + frame_channels: List[List[PulseChannel]], + frame_frequencies: List[float], + dt: float, + frame_indices: List[int] = None, +) -> Union[dict, None]: """ Ties together the frames of the backend and the frequencies of the frames. @@ -584,8 +586,10 @@ def frames_configuration(frame_channels: List[List[PulseChannel]], of frames, i.e. the length of frame_channels. """ if len(frame_frequencies) != len(frame_channels): - raise QiskitError(f'Number of frames {len(frame_channels)} is incompatible with ' - f'the number of frame initial frequencies {len(frame_frequencies)}.') + raise QiskitError( + f"Number of frames {len(frame_channels)} is incompatible with " + f"the number of frame initial frequencies {len(frame_frequencies)}." + ) frames_config = {} for index, channels in enumerate(frame_channels): @@ -595,10 +599,10 @@ def frames_configuration(frame_channels: List[List[PulseChannel]], frame_index = index frames_config[Frame(frame_index)] = { - 'phase': 0.0, - 'frequency': frame_frequencies[index], - 'channels': channels, - 'sample_duration': dt + "phase": 0.0, + "frequency": frame_frequencies[index], + "channels": channels, + "sample_duration": dt, } return frames_config diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index 7f4cbb33ea08..8dc8fdddb455 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -1462,9 +1462,9 @@ def delay(duration: int, channel: chans.Channel, name: Optional[str] = None): def play( - pulse: Union[library.Pulse, library.Signal, np.ndarray], - channel: chans.PulseChannel, - name: Optional[str] = None + pulse: Union[library.Pulse, library.Signal, np.ndarray], + channel: chans.PulseChannel, + name: Optional[str] = None, ): """Play a ``pulse`` on a ``channel``. diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 77d7306bbc59..87960f63c263 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -21,7 +21,8 @@ class Frame: """A frame is a frequency and a phase.""" - prefix = 'f' + + prefix = "f" def __init__(self, index: Union[int, Parameter]): """ @@ -40,12 +41,12 @@ def index(self) -> Union[int, ParameterExpression]: @property def name(self) -> str: """Return the shorthand alias for this frame, which is based on its type and index.""" - return '{}{}'.format(self.__class__.prefix, self._index) + return "{}{}".format(self.__class__.prefix, self._index) def __repr__(self): - return f'{self.__class__.__name__}({self._index})' + return f"{self.__class__.__name__}({self._index})" - def __eq__(self, other: 'Frame') -> bool: + def __eq__(self, other: "Frame") -> bool: """Return True iff self and other are equal, specifically, iff they have the same type and the same index. diff --git a/qiskit/pulse/instructions/play.py b/qiskit/pulse/instructions/play.py index 4f4bc7d5ed14..0e95986a377e 100644 --- a/qiskit/pulse/instructions/play.py +++ b/qiskit/pulse/instructions/play.py @@ -33,7 +33,9 @@ class Play(Instruction): cycle time, dt, of the backend. """ - def __init__(self, pulse: Union[Pulse, Signal], channel: PulseChannel, name: Optional[str] = None): + def __init__( + self, pulse: Union[Pulse, Signal], channel: PulseChannel, name: Optional[str] = None + ): """Create a new pulse instruction. Args: @@ -46,8 +48,10 @@ def __init__(self, pulse: Union[Pulse, Signal], channel: PulseChannel, name: Opt PulseError: If pulse is not a Pulse type. """ if not isinstance(pulse, (Pulse, Signal)): - raise PulseError("The `pulse` argument to `Play` must be of type `library.Pulse` or " - "`library.Signal`.") + raise PulseError( + "The `pulse` argument to `Play` must be of type `library.Pulse` or " + "`library.Signal`." + ) if not isinstance(channel, PulseChannel): raise PulseError( diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index b5c6bd61c143..6aff81168736 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -68,12 +68,12 @@ def parameters(self) -> Dict[str, Any]: parameters = self._pulse.parameters if isinstance(self._frame.index, ParameterExpression): - parameters['index'] = self._frame.index + parameters["index"] = self._frame.index return parameters - def __eq__(self, other: 'Signal') -> bool: + def __eq__(self, other: "Signal") -> bool: return self._pulse == other._pulse and self.frame == other.frame def __repr__(self): - return f'Signal({self._pulse}, {self._frame})' + return f"Signal({self._pulse}, {self._frame})" diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py index fa6b670d04d3..120f571c62b4 100644 --- a/qiskit/pulse/transforms/alignments.py +++ b/qiskit/pulse/transforms/alignments.py @@ -124,8 +124,10 @@ def _push_left_append(self, this: Schedule, other: ScheduleComponent) -> Schedul shared_channels = list(this_channels | other_channels) break - ch_slacks = [this.stop_time - this.ch_stop_time(channel) + other.ch_start_time(channel) - for channel in shared_channels] + ch_slacks = [ + this.stop_time - this.ch_stop_time(channel) + other.ch_start_time(channel) + for channel in shared_channels + ] if ch_slacks: slack_chan = shared_channels[np.argmin(ch_slacks)] @@ -206,8 +208,9 @@ def _push_right_prepend(self, this: ScheduleComponent, other: ScheduleComponent) shared_channels = list(this_channels | other_channels) break - ch_slacks = [this.ch_start_time(channel) - other.ch_stop_time(channel) - for channel in shared_channels] + ch_slacks = [ + this.ch_start_time(channel) - other.ch_stop_time(channel) for channel in shared_channels + ] if ch_slacks: insert_time = min(ch_slacks) + other.start_time diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 51195eb07a6c..7279f3b88e47 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -22,7 +22,6 @@ from qiskit.pulse.frame import Frame - def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Schedule: """ Parse the schedule and replace instructions on Frames by instructions on the @@ -52,10 +51,10 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Sche # Extract shift and set frame operations from the schedule. resolved_frame.set_frame_instructions(schedule) resolved_frames[frame] = resolved_frame - sample_duration = settings['sample_duration'] + sample_duration = settings["sample_duration"] if sample_duration is None: - raise PulseError('Frame configuration does not have a sample duration.') + raise PulseError("Frame configuration does not have a sample duration.") # Used to keep track of the frequency and phase of the channels channel_trackers = {} @@ -79,8 +78,7 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Sche frame = inst.pulse.frame if frame not in resolved_frames: - raise PulseError(f'{frame} is not configured and cannot ' - f'be resolved.') + raise PulseError(f"{frame} is not configured and cannot " f"be resolved.") resolved_frame = resolved_frames[frame] @@ -128,9 +126,15 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Sche if issubclass(type(chan), chans.PulseChannel): sched.insert(time, type(inst)(inst.phase, chan), inplace=True) - elif isinstance(inst, (instructions.Delay, - instructions.Snapshot, instructions.Acquire, - instructions.Directive)): + elif isinstance( + inst, + ( + instructions.Delay, + instructions.Snapshot, + instructions.Acquire, + instructions.Directive, + ), + ): sched.insert(time, inst, inplace=True) else: diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index b07b878f11be..c047d4acb23f 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -92,7 +92,7 @@ def phase(self, time: int) -> float: freq = self.frequency(time) - return (phase + 2*np.pi*freq*(time-last_time)*self._sample_duration) % (2*np.pi) + return (phase + 2 * np.pi * freq * (time - last_time) * self._sample_duration) % (2 * np.pi) def set_frequency(self, time: int, frequency: float): """Insert a new frequency in the time-ordered frequencies.""" @@ -127,8 +127,14 @@ class ResolvedFrame(Tracker): Frame at any given point in time. """ - def __init__(self, frame: Frame, frequency: float, phase: float, - sample_duration: float, channels: List[Channel]): + def __init__( + self, + frame: Frame, + frequency: float, + phase: float, + sample_duration: float, + channels: List[Channel], + ): """ Args: frame: The frame to track. @@ -141,7 +147,7 @@ def __init__(self, frame: Frame, frequency: float, phase: float, PulseError: If there are still parameters in the given frame. """ if isinstance(frame.index, ParameterExpression): - raise PulseError('A parameterized frame cannot be given to ResolvedFrame.') + raise PulseError("A parameterized frame cannot be given to ResolvedFrame.") super().__init__(frame.index, sample_duration) self._frequencies_phases = [(0, frequency, phase)] @@ -149,7 +155,7 @@ def __init__(self, frame: Frame, frequency: float, phase: float, for ch in self._channels: if isinstance(ch.index, ParameterExpression): - raise PulseError('ResolvedFrame does not allow parameterized channels.') + raise PulseError("ResolvedFrame does not allow parameterized channels.") @property def channels(self) -> List[Channel]: @@ -181,11 +187,11 @@ def set_frame_instructions(self, schedule: Schedule): elif isinstance(inst, SetPhase): self.set_phase(time, inst.phase) else: - raise PulseError('Unexpected frame operation.') + raise PulseError("Unexpected frame operation.") def __repr__(self): - sub_str = '[' + ', '.join([ch.__repr__() for ch in self._channels]) + ']' - return f'{self.__class__.__name__}({self._index}, {sub_str})' + sub_str = "[" + ", ".join([ch.__repr__() for ch in self._channels]) + "]" + return f"{self.__class__.__name__}({self._index}, {sub_str})" class ChannelTracker(Tracker): diff --git a/qiskit/pulse/utils.py b/qiskit/pulse/utils.py index f0fe10b05976..b2dea5a01f6a 100644 --- a/qiskit/pulse/utils.py +++ b/qiskit/pulse/utils.py @@ -136,4 +136,4 @@ def validate_index(index: Any) -> None: index = int(index) if not isinstance(index, (int, np.integer)) and index < 0: - raise PulseError('Channel index must be a nonnegative integer') + raise PulseError("Channel index must be a nonnegative integer") diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 222ab5fea37d..5d86ed4b024a 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -31,12 +31,12 @@ def basic(self): frame = pulse.Frame(123) self.assertEqual(frame.index, 123) - self.assertEqual(frame.name, 'f123') + self.assertEqual(frame.name, "f123") def test_parameter(self): """Test parameter assignment.""" parameter_manager = ParameterManager() - param = Parameter('a') + param = Parameter("a") frame = pulse.Frame(param) self.assertTrue(isinstance(frame.index, Parameter)) @@ -85,13 +85,19 @@ def test_phase_advance(self): # Check that the proper phase instructions are added to the frame resolved schedules. resolved = resolve_frames(sched, frames_config).instructions - params = [(0, 0, self.freq0, 1), (160, 160, self.freq1 - self.freq0, 4), - (320, 160, self.freq0 - self.freq1, 7), (480, 160, self.freq1 - self.freq0, 10)] + params = [ + (0, 0, self.freq0, 1), + (160, 160, self.freq1 - self.freq0, 4), + (320, 160, self.freq0 - self.freq1, 7), + (480, 160, self.freq1 - self.freq0, 10), + ] for time, delta, frame_frequency, index in params: - phase = np.angle(np.exp(2.0j * np.pi * frame_frequency * delta * self.dt_)) % (2 * np.pi) + phase = np.angle(np.exp(2.0j * np.pi * frame_frequency * delta * self.dt_)) % ( + 2 * np.pi + ) self.assertEqual(resolved[index][0], time) - self.assertAlmostEqual(resolved[index][1].phase % (2*np.pi), phase, places=8) + self.assertAlmostEqual(resolved[index][1].phase % (2 * np.pi), phase, places=8) def test_phase_advance_with_instructions(self): """Test that the phase advances are properly computed with frame instructions.""" @@ -144,8 +150,9 @@ def test_broadcasting(self): pulse.play(sig, pulse.DriveChannel(3)) pulse.play(sig, pulse.ControlChannel(0)) - frames_config = frames_configuration([[pulse.DriveChannel(3), pulse.ControlChannel(0)]], - [self.freq0], self.dt_) + frames_config = frames_configuration( + [[pulse.DriveChannel(3), pulse.ControlChannel(0)]], [self.freq0], self.dt_ + ) resolved = resolve_frames(sched, frames_config).instructions @@ -158,7 +165,7 @@ def test_broadcasting(self): set_freq = resolved[0][1] self.assertTrue(isinstance(set_freq, pulse.SetFrequency)) self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) - + self.assertEquals(resolved[1][0], 0) set_phase = resolved[1][1] self.assertTrue(isinstance(set_phase, pulse.SetPhase)) diff --git a/test/python/pulse/test_parameters.py b/test/python/pulse/test_parameters.py index f161d5cafa9b..f3e0e5ba18c1 100644 --- a/test/python/pulse/test_parameters.py +++ b/test/python/pulse/test_parameters.py @@ -446,10 +446,10 @@ class TestParameterFrames(QiskitTestCase): def test_frames(self): """Test that we can create a parameterized schedule with Frames.""" - ch_param = Parameter('dx') - f_param = Parameter('fx') - dur = Parameter('duration') - phase = Parameter('phase') + ch_param = Parameter("dx") + f_param = Parameter("fx") + dur = Parameter("duration") + phase = Parameter("phase") signal = Signal(pulse.Gaussian(dur, 0.1, 40), Frame(f_param)) sched = pulse.ScheduleBlock() From a7ae70206e8d3001a3df6616e95de566e982a1f4 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 11 Jun 2021 14:56:56 +0200 Subject: [PATCH 064/111] * Made frame take a string identifier and optional parameter. --- qiskit/pulse/frame.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 87960f63c263..b38bb6ad7302 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -12,39 +12,40 @@ """Implements a Frame.""" -from typing import Union +from typing import Optional, Tuple from qiskit.circuit import Parameter -from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.pulse.utils import validate_index class Frame: """A frame is a frequency and a phase.""" - prefix = "f" - - def __init__(self, index: Union[int, Parameter]): + def __init__(self, identifier: str, parametric_index: Optional[Parameter] = None): """ Args: - index: The index of the frame. + identifier: The index of the frame. + parametric_index: An optional parameter to specify the numeric part of the index. """ - validate_index(index) - self._index = index - self._hash = hash((type(self), self._index)) + validate_index(parametric_index) + self._identifier = (identifier, parametric_index) + self._hash = hash((type(self), self._identifier)) @property - def index(self) -> Union[int, ParameterExpression]: + def identifier(self) -> Tuple: """Return the index of this frame. The index is a label for a frame.""" - return self._index + return self._identifier @property def name(self) -> str: """Return the shorthand alias for this frame, which is based on its type and index.""" - return "{}{}".format(self.__class__.prefix, self._index) + if self._identifier[1] is None: + return f"{self._identifier[0]}" + + return f"{self._identifier[0]}{self._identifier[1].name}" def __repr__(self): - return f"{self.__class__.__name__}({self._index})" + return f"{self.__class__.__name__}({self.name})" def __eq__(self, other: "Frame") -> bool: """Return True iff self and other are equal, specifically, iff they have the same type @@ -56,7 +57,7 @@ def __eq__(self, other: "Frame") -> bool: Returns: True iff equal. """ - return type(self) is type(other) and self.index == other.index + return type(self) is type(other) and self._identifier == other._identifier def __hash__(self): return self._hash From d8aa27fd87162bd18f11c7f08eaafacaeac8578e Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 11 Jun 2021 15:32:27 +0200 Subject: [PATCH 065/111] * Changed the backend configuration. --- .../providers/models/backendconfiguration.py | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/qiskit/providers/models/backendconfiguration.py b/qiskit/providers/models/backendconfiguration.py index 5b3b45fe0b39..222a10cd32bd 100644 --- a/qiskit/providers/models/backendconfiguration.py +++ b/qiskit/providers/models/backendconfiguration.py @@ -20,6 +20,7 @@ from qiskit.exceptions import QiskitError from qiskit.providers.exceptions import BackendConfigurationError +from qiskit.pulse.frame import Frame from qiskit.pulse.channels import ( AcquireChannel, Channel, @@ -870,25 +871,19 @@ def control(self, qubits: Iterable[int] = None, channel: int = None) -> List[Con f"This backend - '{self.backend_name}' does not provide channel information." ) from ex - def frames(self) -> List[List[Channel]]: + def frames(self) -> Dict[str, Frame]: """ Gets the frames that the backend supports. Returns: - frames: A List of lists. Sublist i is a list of channel names that belong - to frame i. + frames: A dictionary of frames with the identifier as key. """ - n_qubits = self.n_qubits - frames = [] - for qubit in range(n_qubits): - frame_ = [DriveChannel(qubit)] - for ctrl in range(n_qubits): - try: - frame_ += list(self.control((ctrl, qubit))) - except BackendConfigurationError: - pass - - frames.append(frame_) + frames = {} + for qubit in range(self.n_qubits): + frames[f"Q{qubit}"] = Frame(f"Q{qubit}") + + for meas_idx, _ in self.meas_lo_range: + frames[f"M{meas_idx}"] = Frame(f"M{meas_idx}") return frames From 31c31c381a32d54428ba24fbda356f47197b58c4 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 11 Jun 2021 18:54:38 +0200 Subject: [PATCH 066/111] * Refactored the frames config. --- qiskit/compiler/assembler.py | 90 +++---------------- .../providers/models/backendconfiguration.py | 17 ---- qiskit/providers/models/pulsedefaults.py | 18 ++++ 3 files changed, 30 insertions(+), 95 deletions(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index f75d2ed44a72..2b39a214f5fb 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -76,7 +76,7 @@ def assemble( parameter_binds: Optional[List[Dict[Parameter, float]]] = None, parametric_pulses: Optional[List[str]] = None, init_qubits: bool = True, - frames_config: Dict[int, Dict] = None, + frames_config: Dict[Frame, Dict] = None, **run_config: Dict, ) -> Qobj: """Assemble a list of circuits or pulse schedules into a ``Qobj``. @@ -143,11 +143,10 @@ def assemble( ['gaussian', 'constant'] init_qubits: Whether to reset the qubits to the ground state for each shot. Default: ``True``. - frames_config: Dictionary of user provided frames configuration. The key is the index - of the frame and the value is a dictionary with the configuration of the frame - which must be of the form {'frame': Frame, 'phase': float, 'frequency': float, - 'channels': List[Channels]}. This object will be used to initialize ResolvedFrame - instance to resolve the frames in the Schedule. + frames_config: Dictionary of user provided frames configuration. The key is the frame + and the value is a dictionary with the configuration of the frame which must be of + the form {'frequency': float, 'purpose': str, 'dt': float}. This object is be used + to initialize ResolvedFrame instance to resolve the frames in the Schedule. **run_config: Extra arguments used to configure the run (e.g., for Aer configurable backends). Refer to the backend documentation for details on these arguments. @@ -440,37 +439,20 @@ def _parse_pulse_args( meas_map = meas_map or getattr(backend_config, "meas_map", None) - schedule_los = schedule_los or [] - if isinstance(schedule_los, (LoConfig, dict)): - schedule_los = [schedule_los] - - # Convert to LoConfig if LO configuration supplied as dictionary - schedule_los = [ - lo_config if isinstance(lo_config, LoConfig) else LoConfig(lo_config) - for lo_config in schedule_los - ] - - if not qubit_lo_freq and hasattr(backend_default, "qubit_freq_est"): - qubit_lo_freq = backend_default.qubit_freq_est - if not meas_lo_freq and hasattr(backend_default, "meas_freq_est"): - meas_lo_freq = backend_default.meas_freq_est - - qubit_lo_range = qubit_lo_range or getattr(backend_config, "qubit_lo_range", None) - meas_lo_range = meas_lo_range or getattr(backend_config, "meas_lo_range", None) + frames_config_ = {} + if backend: + frames_config_ = getattr(backend.defaults(), "frames", {}) - frames_config_ = None - if hasattr(backend_config, "frames"): - frames_config_ = frames_configuration( - backend_config.frames(), qubit_lo_freq, backend_config.dt - ) + for config in frames_config_.values(): + config["dt"] = backend_config.dt if frames_config is None: frames_config = frames_config_ else: - for frame, settings in frames_config_.items(): + for frame, config in frames_config_.items(): # Do not override the frames provided by the user. if frame not in frames_config: - frames_config[frame] = settings + frames_config[frame] = config dynamic_reprate_enabled = getattr(backend_config, "dynamic_reprate_enabled", False) @@ -631,51 +613,3 @@ def _expand_parameters(circuits, run_config): run_config.parameter_binds = [] return circuits, run_config - - -def frames_configuration( - frame_channels: List[List[PulseChannel]], - frame_frequencies: List[float], - dt: float, - frame_indices: List[int] = None, -) -> Union[dict, None]: - """ - Ties together the frames of the backend and the frequencies of the frames. - - Args: - frame_channels: A List of lists. Sublist i is a list of channel names - that frame i will broadcast on. - frame_frequencies: A list of starting frequencies for each frame. - dt: duration of a sample in the waveforms. - frame_indices: The indices of the frames. If None is given these will be - in ascending order starting from 0. - - Returns: - frames_config: A dictionary with the frame index as key and the values are - a dict which can be used to initialized a ResolvedFrame. - - Raises: - QiskitError: if the number of frame frequencies is not the same as the number - of frames, i.e. the length of frame_channels. - """ - if len(frame_frequencies) != len(frame_channels): - raise QiskitError( - f"Number of frames {len(frame_channels)} is incompatible with " - f"the number of frame initial frequencies {len(frame_frequencies)}." - ) - - frames_config = {} - for index, channels in enumerate(frame_channels): - if frame_indices: - frame_index = frame_indices[index] - else: - frame_index = index - - frames_config[Frame(frame_index)] = { - "phase": 0.0, - "frequency": frame_frequencies[index], - "channels": channels, - "sample_duration": dt, - } - - return frames_config diff --git a/qiskit/providers/models/backendconfiguration.py b/qiskit/providers/models/backendconfiguration.py index 222a10cd32bd..20221d9ecbc5 100644 --- a/qiskit/providers/models/backendconfiguration.py +++ b/qiskit/providers/models/backendconfiguration.py @@ -20,7 +20,6 @@ from qiskit.exceptions import QiskitError from qiskit.providers.exceptions import BackendConfigurationError -from qiskit.pulse.frame import Frame from qiskit.pulse.channels import ( AcquireChannel, Channel, @@ -871,22 +870,6 @@ def control(self, qubits: Iterable[int] = None, channel: int = None) -> List[Con f"This backend - '{self.backend_name}' does not provide channel information." ) from ex - def frames(self) -> Dict[str, Frame]: - """ - Gets the frames that the backend supports. - - Returns: - frames: A dictionary of frames with the identifier as key. - """ - frames = {} - for qubit in range(self.n_qubits): - frames[f"Q{qubit}"] = Frame(f"Q{qubit}") - - for meas_idx, _ in self.meas_lo_range: - frames[f"M{meas_idx}"] = Frame(f"M{meas_idx}") - - return frames - def get_channel_qubits(self, channel: Channel) -> List[int]: """ Return a list of indices for qubits which are operated on directly by the given ``channel``. diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index 8cd7686d0ef8..3df82a2f0894 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -20,6 +20,7 @@ from qiskit.pulse.schedule import Schedule from qiskit.qobj import PulseLibraryItem, PulseQobjInstruction from qiskit.qobj.converters import QobjToInstructionConverter +from qiskit.pulse.frame import Frame class MeasurementKernel: @@ -278,6 +279,23 @@ def from_dict(cls, data): in_data["discriminator"] = Discriminator.from_dict(in_data.pop("discriminator")) return cls(**in_data) + @property + def frames(self) -> Dict[Frame, Dict]: + """Get the frames supported by the backend. + + Returns: + frames: A list of dicts, each dict defines a frame, its frequency, and its purpose. + """ + frames = {} + for qubit, freq in enumerate(self.qubit_freq_est): + frames[Frame(f"Q{qubit}")] = {"frequency": freq, "purpose": f"Frame of qubit {qubit}"} + + + for meas, freq in enumerate(self.meas_freq_est): + frames[Frame(f"M{meas}")] = {"frequency": freq, "purpose": f"Frame of meas {meas}"} + + return frames + def __str__(self): qubit_freqs = [freq / 1e9 for freq in self.qubit_freq_est] meas_freqs = [freq / 1e9 for freq in self.meas_freq_est] From 5814d37fdd266ae5ddce2a1c5abaa3e59aa2c3c0 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 11 Jun 2021 19:14:31 +0200 Subject: [PATCH 067/111] * Refactored ResolvedFrame. --- qiskit/compiler/assembler.py | 7 ++-- qiskit/pulse/transforms/resolved_frame.py | 50 ++++++++++------------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index 2b39a214f5fb..b0c0bc4e43cc 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -145,8 +145,9 @@ def assemble( Default: ``True``. frames_config: Dictionary of user provided frames configuration. The key is the frame and the value is a dictionary with the configuration of the frame which must be of - the form {'frequency': float, 'purpose': str, 'dt': float}. This object is be used - to initialize ResolvedFrame instance to resolve the frames in the Schedule. + the form {'frequency': float, 'purpose': str, 'sample_duration': float}. This + object is be used to initialize ResolvedFrame instance to resolve the frames in + the Schedule. **run_config: Extra arguments used to configure the run (e.g., for Aer configurable backends). Refer to the backend documentation for details on these arguments. @@ -444,7 +445,7 @@ def _parse_pulse_args( frames_config_ = getattr(backend.defaults(), "frames", {}) for config in frames_config_.values(): - config["dt"] = backend_config.dt + config["sample_duration"] = backend_config.dt if frames_config is None: frames_config = frames_config_ diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index c047d4acb23f..7ab82341e513 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -13,11 +13,11 @@ """Implements a Frame.""" from abc import ABC -from typing import List +from typing import Optional import numpy as np from qiskit.circuit.parameterexpression import ParameterExpression -from qiskit.pulse.channels import Channel, PulseChannel +from qiskit.pulse.channels import PulseChannel from qiskit.pulse.frame import Frame from qiskit.pulse.schedule import Schedule from qiskit.pulse.instructions.frequency import SetFrequency, ShiftFrequency @@ -31,22 +31,21 @@ class Tracker(ABC): or a pulse channel in a given schedule. """ - def __init__(self, index: int, sample_duration: float): + def __init__(self, identifier: str, sample_duration: float): """ Args: - index: The index of the Tracker. Corresponds to the index of a - :class:`~qiskit.pulse.Frame` or a channel. + identifier: The identifier of the Tracker. sample_duration: Duration of a sample. """ - self._index = index + self._identifier = identifier self._frequencies_phases = [] # List of (time, frequency, phase) tuples self._instructions = {} self._sample_duration = sample_duration @property - def index(self) -> int: - """Return the index of the tracker.""" - return self._index + def identifier(self) -> str: + """Return the identifier of the tracker.""" + return self._identifier def frequency(self, time: int) -> float: """ @@ -131,36 +130,32 @@ def __init__( self, frame: Frame, frequency: float, - phase: float, sample_duration: float, - channels: List[Channel], + phase: float = 0.0, + purpose: Optional[str] = None, ): """ Args: frame: The frame to track. frequency: The initial frequency of the frame. - phase: The initial phase of the frame. sample_duration: Duration of a sample. - channels: The list of channels on which the frame instructions apply. + phase: The initial phase of the frame. + purpose: A human readable description of the frame. Raises: PulseError: If there are still parameters in the given frame. """ - if isinstance(frame.index, ParameterExpression): - raise PulseError("A parameterized frame cannot be given to ResolvedFrame.") + if isinstance(frame.identifier[1], ParameterExpression): + raise PulseError("A parameterized frame cannot initialize a ResolvedFrame.") - super().__init__(frame.index, sample_duration) + super().__init__(frame.name, sample_duration) self._frequencies_phases = [(0, frequency, phase)] - self._channels = channels - - for ch in self._channels: - if isinstance(ch.index, ParameterExpression): - raise PulseError("ResolvedFrame does not allow parameterized channels.") + self._purpose = purpose @property - def channels(self) -> List[Channel]: - """Returns the channels that this frame ties together.""" - return self._channels + def purpose(self) -> str: + """Return the purpose of the frame.""" + return self.purpose def set_frame_instructions(self, schedule: Schedule): """ @@ -177,7 +172,7 @@ def set_frame_instructions(self, schedule: Schedule): frame_instructions = schedule.filter(instruction_types=frame_instruction_types) for time, inst in frame_instructions.instructions: - if Frame(self._index) == inst.operands[1]: + if Frame(self.identifier) == inst.operands[1]: if isinstance(inst, ShiftFrequency): self.set_frequency(time, self.frequency(time) + inst.frequency) elif isinstance(inst, SetFrequency): @@ -190,8 +185,7 @@ def set_frame_instructions(self, schedule: Schedule): raise PulseError("Unexpected frame operation.") def __repr__(self): - sub_str = "[" + ", ".join([ch.__repr__() for ch in self._channels]) + "]" - return f"{self.__class__.__name__}({self._index}, {sub_str})" + return f"{self.__class__.__name__}({self.identifier}, {self.frequency(0)})" class ChannelTracker(Tracker): @@ -203,7 +197,7 @@ def __init__(self, channel: PulseChannel, sample_duration: float): channel: The channel that this tracker tracks. sample_duration: Duration of a sample. """ - super().__init__(channel.index, sample_duration) + super().__init__(channel.name, sample_duration) self._channel = channel def is_initialized(self) -> bool: From 92d40b98431850faa1e7946bce5116193aa4a135 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 11 Jun 2021 19:48:37 +0200 Subject: [PATCH 068/111] * Improved parameter treatment in frame. --- qiskit/pulse/frame.py | 13 ++++++++++++- qiskit/pulse/library/signal.py | 8 +++++--- qiskit/pulse/parameter_manager.py | 10 +++++----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index b38bb6ad7302..59fd07761f4f 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -16,6 +16,7 @@ from qiskit.circuit import Parameter from qiskit.pulse.utils import validate_index +from qiskit.pulse.exceptions import PulseError class Frame: @@ -27,7 +28,12 @@ def __init__(self, identifier: str, parametric_index: Optional[Parameter] = None identifier: The index of the frame. parametric_index: An optional parameter to specify the numeric part of the index. """ - validate_index(parametric_index) + if parametric_index is not None: + validate_index(parametric_index) + + if not isinstance(identifier, str): + raise PulseError(f"Frame identifiers must be string. Got {type(identifier)}.") + self._identifier = (identifier, parametric_index) self._hash = hash((type(self), self._identifier)) @@ -36,6 +42,11 @@ def identifier(self) -> Tuple: """Return the index of this frame. The index is a label for a frame.""" return self._identifier + @property + def prefix(self) -> str: + """Return the prefix of the frame.""" + return self._identifier[0] + @property def name(self) -> str: """Return the shorthand alias for this frame, which is based on its type and index.""" diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index 6aff81168736..2f43015cd926 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -60,15 +60,17 @@ def duration(self) -> Union[int, ParameterExpression]: def is_parameterized(self) -> bool: """Determine if there are any parameters in the Signal.""" - return self._pulse.is_parameterized() or isinstance(self._frame.index, ParameterExpression) + param_id = self._frame.identifier[1] + return self._pulse.is_parameterized() or isinstance(param_id, ParameterExpression) @property def parameters(self) -> Dict[str, Any]: """Return a list of parameters in the Signal.""" parameters = self._pulse.parameters + param_id = self._frame.identifier[1] - if isinstance(self._frame.index, ParameterExpression): - parameters["index"] = self._frame.index + if isinstance(param_id, ParameterExpression): + parameters["index"] = param_id return parameters diff --git a/qiskit/pulse/parameter_manager.py b/qiskit/pulse/parameter_manager.py index c3f36c0763b3..45ee7cafb434 100644 --- a/qiskit/pulse/parameter_manager.py +++ b/qiskit/pulse/parameter_manager.py @@ -209,13 +209,13 @@ def visit_Channel(self, node: channels.Channel): def visit_Frame(self, node: Frame): """Assign parameters to ``Frame`` object.""" - if isinstance(node.index, ParameterExpression): - new_index = self._assign_parameter_expression(node.index) + if isinstance(node.identifier[1], ParameterExpression): + new_index = self._assign_parameter_expression(node.identifier[1]) if not isinstance(new_index, ParameterExpression): validate_index(new_index) - return node.__class__(index=new_index) + return node.__class__(node.prefix + str(new_index)) return node @@ -348,8 +348,8 @@ def visit_Signal(self, node: Signal): def visit_Frame(self, node: Frame): """Get parameters from ``Frame`` object.""" - if isinstance(node.index, ParameterExpression): - self._add_parameters(node.index) + if isinstance(node.identifier[1], ParameterExpression): + self._add_parameters(node.identifier[1]) def visit_ParametricPulse(self, node: ParametricPulse): """Get parameters from ``ParametricPulse`` object.""" From f9c90e515b0f881ca92da9e09785b000929bd90c Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 11 Jun 2021 20:03:44 +0200 Subject: [PATCH 069/111] * Removed frame channels in transforms. --- qiskit/pulse/transforms/frames.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 7279f3b88e47..dfa888426b89 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -12,17 +12,18 @@ """Replace a schedule with frames by one with instructions on PulseChannels only.""" -from typing import Dict +from typing import Dict, Union -from qiskit.pulse.schedule import Schedule +from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.pulse.transforms.resolved_frame import ResolvedFrame, ChannelTracker +from qiskit.pulse.transforms.canonicalization import block_to_schedule from qiskit.pulse.library import Signal from qiskit.pulse.exceptions import PulseError from qiskit.pulse import channels as chans, instructions from qiskit.pulse.frame import Frame -def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Schedule: +def resolve_frames(schedule: Union[Schedule, ScheduleBlock], frames_config: Dict[Frame, Dict]) -> Schedule: """ Parse the schedule and replace instructions on Frames by instructions on the appropriate channels. @@ -40,6 +41,9 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Sche Raises: PulseError: if a frame is not configured. """ + if isinstance(schedule, ScheduleBlock): + schedule = block_to_schedule(schedule) + if frames_config is None: return schedule @@ -62,12 +66,6 @@ def resolve_frames(schedule: Schedule, frames_config: Dict[Frame, Dict]) -> Sche if isinstance(ch, chans.PulseChannel): channel_trackers[ch] = ChannelTracker(ch, sample_duration) - # Add the channels that the frames broadcast on. - for resolved_frame in resolved_frames.values(): - for ch in resolved_frame.channels: - if ch not in channel_trackers: - channel_trackers[ch] = ChannelTracker(ch, sample_duration) - sched = Schedule(name=schedule.name, metadata=schedule.metadata) for time, inst in schedule.instructions: From b5641d348de67448122a700e07cff7f426f9e238 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 11 Jun 2021 20:19:44 +0200 Subject: [PATCH 070/111] * Amend assemble_schedule. --- qiskit/assembler/assemble_schedules.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index cd0c2bd0fa98..c2ba5b9fe3c2 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -341,13 +341,12 @@ def _assemble_config( if m_los: qobj_config["meas_lo_freq"] = [freq / 1e9 for freq in m_los] - # frames config + # frames config: replace the frame instance by its name. frames_config = qobj_config.get("frames_config", None) if frames_config: frames_config_ = {} for frame, settings in frames_config.items(): frames_config_[frame.name] = settings - frames_config_[frame.name]["channels"] = [ch.name for ch in settings["channels"]] qobj_config["frames_config"] = frames_config_ From 5a9ccd393e1604316ceab5de788f45d7761b820a Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 14 Jun 2021 16:40:46 +0200 Subject: [PATCH 071/111] * Align and refactor tests. --- qiskit/pulse/builder.py | 4 +- qiskit/pulse/transforms/resolved_frame.py | 7 +- test/python/pulse/test_builder.py | 24 ++--- test/python/pulse/test_frames.py | 126 +++++++++++++++------- test/python/pulse/test_instructions.py | 8 +- test/python/pulse/test_parameters.py | 4 +- test/python/pulse/test_pulse_lib.py | 4 +- 7 files changed, 114 insertions(+), 63 deletions(-) diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index 614e0e44799c..9c6e6db28d9e 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -908,7 +908,7 @@ def align_left(ignore_frames: bool = False) -> ContextManager[None]: None """ builder = _active_builder() - builder.push_context(transforms.AlignLeft()) + builder.push_context(transforms.AlignLeft(ignore_frames)) try: yield finally: @@ -952,7 +952,7 @@ def align_right(ignore_frames: bool = False) -> AlignmentKind: None """ builder = _active_builder() - builder.push_context(transforms.AlignRight()) + builder.push_context(transforms.AlignRight(ignore_frames)) try: yield finally: diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 7ab82341e513..0f9f4a65bfe6 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -94,7 +94,12 @@ def phase(self, time: int) -> float: return (phase + 2 * np.pi * freq * (time - last_time) * self._sample_duration) % (2 * np.pi) def set_frequency(self, time: int, frequency: float): - """Insert a new frequency in the time-ordered frequencies.""" + """Insert a new frequency in the time-ordered frequencies. + + Args: + time: The time in samples (i.e. measured in units of dt). + frequency: The frequency to which self is set after the given time. + """ insert_idx = 0 for idx, time_freq in enumerate(self._frequencies_phases): if time_freq[0] < time: diff --git a/test/python/pulse/test_builder.py b/test/python/pulse/test_builder.py index cfeb98bdc46e..41aa47ef4afb 100644 --- a/test/python/pulse/test_builder.py +++ b/test/python/pulse/test_builder.py @@ -1235,31 +1235,31 @@ class TestBuilderFrames(TestBuilder): def test_simple_frame_schedule(self): """Test basic schedule construction with frames.""" - signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(0)) + signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q0")) with pulse.build() as sched: with pulse.align_left(): pulse.play(signal, pulse.DriveChannel(0)) - pulse.shift_phase(1.57, pulse.Frame(0)) + pulse.shift_phase(1.57, pulse.Frame("Q0")) pulse.play(signal, pulse.DriveChannel(0)) play_gaus = pulse.Play(signal, pulse.DriveChannel(0)) self.assertEqual(sched.instructions[0][0], 0) self.assertEqual(sched.instructions[0][1], play_gaus) self.assertEqual(sched.instructions[1][0], 160) - self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame(0))) + self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame("Q0"))) self.assertEqual(sched.instructions[2][0], 160) self.assertEqual(sched.instructions[2][1], play_gaus) def test_ignore_frames(self): """Test the behavior of ignoring frames in the alignments context.""" - signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(0)) + signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q0")) with pulse.build() as sched: with pulse.align_right(ignore_frames=True): pulse.play(signal, pulse.DriveChannel(0)) - pulse.shift_phase(1.57, pulse.Frame(0)) - pulse.shift_phase(1.57, pulse.Frame(1)) + pulse.shift_phase(1.57, pulse.Frame("Q0")) + pulse.shift_phase(1.57, pulse.Frame("Q1")) pulse.play(signal, pulse.DriveChannel(0)) play_gaus = pulse.Play(signal, pulse.DriveChannel(0)) @@ -1269,15 +1269,15 @@ def test_ignore_frames(self): self.assertEqual(sched.instructions[1][0], 160) self.assertEqual(sched.instructions[1][1], play_gaus) self.assertEqual(sched.instructions[2][0], 320) - self.assertEqual(sched.instructions[2][1], pulse.ShiftPhase(1.57, pulse.Frame(0))) + self.assertEqual(sched.instructions[2][1], pulse.ShiftPhase(1.57, pulse.Frame("Q0"))) self.assertEqual(sched.instructions[3][0], 320) - self.assertEqual(sched.instructions[3][1], pulse.ShiftPhase(1.57, pulse.Frame(1))) + self.assertEqual(sched.instructions[3][1], pulse.ShiftPhase(1.57, pulse.Frame("Q1"))) with pulse.build() as sched: with pulse.align_right(ignore_frames=False): pulse.play(signal, pulse.DriveChannel(0)) - pulse.shift_phase(1.57, pulse.Frame(0)) - pulse.shift_phase(1.57, pulse.Frame(1)) + pulse.shift_phase(1.57, pulse.Frame("Q0")) + pulse.shift_phase(1.57, pulse.Frame("Q1")) pulse.play(signal, pulse.DriveChannel(0)) play_gaus = pulse.Play(signal, pulse.DriveChannel(0)) @@ -1285,8 +1285,8 @@ def test_ignore_frames(self): self.assertEqual(sched.instructions[0][0], 0) self.assertEqual(sched.instructions[0][1], play_gaus) self.assertEqual(sched.instructions[1][0], 160) - self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame(0))) + self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame("Q0"))) self.assertEqual(sched.instructions[2][0], 160) - self.assertEqual(sched.instructions[2][1], pulse.ShiftPhase(1.57, pulse.Frame(1))) + self.assertEqual(sched.instructions[2][1], pulse.ShiftPhase(1.57, pulse.Frame("Q1"))) self.assertEqual(sched.instructions[3][0], 160) self.assertEqual(sched.instructions[3][1], play_gaus) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 5d86ed4b024a..6537f35ce603 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -15,10 +15,9 @@ import numpy as np from qiskit.circuit import Parameter -from qiskit.compiler.assembler import frames_configuration import qiskit.pulse as pulse from qiskit.test import QiskitTestCase -from qiskit.pulse.transforms import resolve_frames +from qiskit.pulse.transforms import resolve_frames, block_to_schedule from qiskit.pulse.transforms.resolved_frame import ResolvedFrame from qiskit.pulse.parameter_manager import ParameterManager @@ -29,22 +28,24 @@ class TestFrame(QiskitTestCase): def basic(self): """Test basic functionality of frames.""" - frame = pulse.Frame(123) - self.assertEqual(frame.index, 123) - self.assertEqual(frame.name, "f123") + frame = pulse.Frame("Q13") + self.assertEqual(frame.identifier, "Q13") + self.assertEqual(frame.name, "Q13") def test_parameter(self): """Test parameter assignment.""" parameter_manager = ParameterManager() param = Parameter("a") - frame = pulse.Frame(param) - self.assertTrue(isinstance(frame.index, Parameter)) + frame = pulse.Frame("Q", param) + self.assertEqual(frame.identifier, ("Q", param)) + self.assertEqual(frame.prefix, "Q") parameter_manager.update_parameter_table(frame) new_frame = parameter_manager.assign_parameters(frame, {param: 123}) - self.assertEqual(new_frame, pulse.Frame(123)) - self.assertEqual(frame, pulse.Frame(param)) + self.assertEqual(new_frame, pulse.Frame("Q123")) + self.assertEqual(new_frame.identifier, ("Q123", None)) + self.assertEqual(frame, pulse.Frame("Q", param)) class TestResolvedFrames(QiskitTestCase): @@ -56,12 +57,32 @@ def setUp(self): self.freq0 = 0.093e9 # Frequency of frame 0 self.freq1 = 0.125e9 # Frequency of frame 1 + def test_phase(self): + """Test the phase of the resolved frames.""" + two_pi_dt = 2.0j * np.pi * self.dt_ + + r_frame = ResolvedFrame(pulse.Frame("Q0"), 5.5e9, self.dt_) + r_frame.set_frequency(99, 5.2e9) + + for time in [0, 55, 98]: + phase = np.angle(np.exp(two_pi_dt * 5.5e9 * time)) % (2 * np.pi) + self.assertAlmostEqual(r_frame.phase(time), phase, places=8) + self.assertEqual(r_frame.frequency(time), 5.5e9) + + for time in [100, 112]: + phase = np.angle(np.exp(two_pi_dt * (5.5e9 * 99 + 5.2e9 * (time - 99)))) % (2 * np.pi) + self.assertAlmostEqual(r_frame.phase(time), phase, places=8) + def test_phase_advance(self): - """Test that phases are properly set when frames are resolved.""" + """Test that phases are properly set when frames are resolved. + + Here we apply four pulses in two frame and alternate between them. + The resolved schedule will have shift-phase instructions. + """ d0 = pulse.DriveChannel(0) - sig0 = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(0)) - sig1 = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(1)) + sig0 = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q0")) + sig1 = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q1")) with pulse.build() as sched: pulse.play(sig0, d0) @@ -69,10 +90,21 @@ def test_phase_advance(self): pulse.play(sig0, d0) pulse.play(sig1, d0) - frames_config = frames_configuration([[d0], [d0]], [self.freq0, self.freq1], self.dt_) - - frame0_ = ResolvedFrame(pulse.Frame(1), self.freq0, 0.0, self.dt_, []) - frame1_ = ResolvedFrame(pulse.Frame(2), self.freq1, 0.0, self.dt_, []) + frames_config = { + pulse.Frame("Q0"): { + "frequency": self.freq0, + "purpose": "Frame of qubit 0.", + "sample_duration": self.dt_ + }, + pulse.Frame("Q1"): { + "frequency": self.freq1, + "purpose": "Frame of qubit 1.", + "sample_duration": self.dt_ + } + } + + frame0_ = ResolvedFrame(pulse.Frame("Q0"), self.freq0, self.dt_) + frame1_ = ResolvedFrame(pulse.Frame("Q1"), self.freq1, self.dt_) # Check that the resolved frames are tracking phases properly for time in [0, 160, 320, 480]: @@ -86,30 +118,40 @@ def test_phase_advance(self): resolved = resolve_frames(sched, frames_config).instructions params = [ - (0, 0, self.freq0, 1), - (160, 160, self.freq1 - self.freq0, 4), - (320, 160, self.freq0 - self.freq1, 7), - (480, 160, self.freq1 - self.freq0, 10), + (0, 160, self.freq0, self.freq0, 1), + (160, 160, self.freq1, self.freq0, 4), + (320, 160, self.freq0, self.freq1, 7), + (480, 160, self.freq1, self.freq0, 10), ] - for time, delta, frame_frequency, index in params: - phase = np.angle(np.exp(2.0j * np.pi * frame_frequency * delta * self.dt_)) % ( - 2 * np.pi - ) + channel_phase = 0.0 + for time, delta, frame_freq, other_freq, index in params: + wanted_phase = np.angle(np.exp(2.0j * np.pi * frame_freq * time * self.dt_)) % (2 * np.pi) + + phase_diff = (wanted_phase - channel_phase) % (2 * np.pi) + + if time == 0: + self.assertTrue(isinstance(resolved[index][1], pulse.SetPhase)) + else: + self.assertTrue(isinstance(resolved[index][1], pulse.ShiftPhase)) + + self.assertEqual(resolved[index][0], time) - self.assertAlmostEqual(resolved[index][1].phase % (2 * np.pi), phase, places=8) + self.assertAlmostEqual(resolved[index][1].phase % (2 * np.pi), phase_diff, places=8) + + channel_phase += np.angle(np.exp(2.0j * np.pi * frame_freq * delta * self.dt_)) % (2 * np.pi) def test_phase_advance_with_instructions(self): """Test that the phase advances are properly computed with frame instructions.""" - sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(0)) + sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q0")) with pulse.build() as sched: pulse.play(sig, pulse.DriveChannel(0)) - pulse.shift_phase(1.0, pulse.Frame(0)) + pulse.shift_phase(1.0, pulse.Frame("Q0")) - frame = ResolvedFrame(pulse.Frame(0), self.freq0, 0.0, self.dt_, []) - frame.set_frame_instructions(sched) + frame = ResolvedFrame(pulse.Frame("Q0"), self.freq0, self.dt_) + frame.set_frame_instructions(block_to_schedule(sched)) self.assertAlmostEqual(frame.phase(0), 0.0, places=8) @@ -127,7 +169,7 @@ def test_phase_advance_with_instructions(self): def test_set_frequency(self): """Test setting the frequency of the resolved frame.""" - frame = ResolvedFrame(pulse.Frame(0), self.freq1, 0.0, self.dt_, []) + frame = ResolvedFrame(pulse.Frame("Q0"), self.freq1, self.dt_) frame.set_frequency(16, self.freq0) frame.set_frequency(10, self.freq1) frame.set_frequency(10, self.freq1) @@ -140,19 +182,23 @@ def test_set_frequency(self): def test_broadcasting(self): """Test that resolved frames broadcast to control channels.""" - sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame(0)) + sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q0")) with pulse.build() as sched: with pulse.align_left(): pulse.play(sig, pulse.DriveChannel(3)) - pulse.shift_phase(1.23, pulse.Frame(0)) + pulse.shift_phase(1.23, pulse.Frame("Q0")) with pulse.align_left(ignore_frames=True): pulse.play(sig, pulse.DriveChannel(3)) pulse.play(sig, pulse.ControlChannel(0)) - frames_config = frames_configuration( - [[pulse.DriveChannel(3), pulse.ControlChannel(0)]], [self.freq0], self.dt_ - ) + frames_config = { + pulse.Frame("Q0"): { + "frequency": self.freq0, + "purpose": "Frame of qubit 0.", + "sample_duration": self.dt_ + } + } resolved = resolve_frames(sched, frames_config).instructions @@ -161,18 +207,18 @@ def test_broadcasting(self): # Check that the frame resolved schedule has the correct phase and frequency instructions # at the right place. # First, ensure that resolved starts with a SetFrequency and SetPhase. - self.assertEquals(resolved[0][0], 0) + self.assertEqual(resolved[0][0], 0) set_freq = resolved[0][1] self.assertTrue(isinstance(set_freq, pulse.SetFrequency)) self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) - self.assertEquals(resolved[1][0], 0) + self.assertEqual(resolved[1][0], 0) set_phase = resolved[1][1] self.assertTrue(isinstance(set_phase, pulse.SetPhase)) self.assertAlmostEqual(set_phase.phase, 0.0, places=8) # Next, check that we do phase shifts on the DriveChannel after the first Gaussian. - self.assertEquals(resolved[3][0], 160) + self.assertEqual(resolved[3][0], 160) shift_phase = resolved[3][1] self.assertTrue(isinstance(shift_phase, pulse.ShiftPhase)) self.assertAlmostEqual(shift_phase.phase, 1.23, places=8) @@ -180,12 +226,12 @@ def test_broadcasting(self): # Up to now, no pulse has been applied on the ControlChannel so we should # encounter a Set instructions at time 160 which is when the first pulse # is played on ControlChannel(0) - self.assertEquals(resolved[4][0], 160) + self.assertEqual(resolved[4][0], 160) set_freq = resolved[4][1] self.assertTrue(isinstance(set_freq, pulse.SetFrequency)) self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) - self.assertEquals(resolved[5][0], 160) + self.assertEqual(resolved[5][0], 160) set_phase = resolved[5][1] self.assertTrue(isinstance(set_phase, pulse.SetPhase)) self.assertAlmostEqual(set_phase.phase, phase, places=8) diff --git a/test/python/pulse/test_instructions.py b/test/python/pulse/test_instructions.py index 65c9779f9a06..8a14680a9c3c 100644 --- a/test/python/pulse/test_instructions.py +++ b/test/python/pulse/test_instructions.py @@ -150,9 +150,9 @@ def test_freq(self): def test_frame(self): """Test the basic shift phase on a Frame.""" - set_freq = instructions.SetFrequency(4.5e9, Frame(123)) + set_freq = instructions.SetFrequency(4.5e9, Frame("Q123")) - self.assertEqual(set_freq.channel, Frame(123)) + self.assertEqual(set_freq.channel, Frame("Q123")) class TestShiftPhase(QiskitTestCase): @@ -177,9 +177,9 @@ def test_default(self): def test_frame(self): """Test the basic shift phase on a Frame.""" - shift_phase = instructions.ShiftPhase(1.57, Frame(123)) + shift_phase = instructions.ShiftPhase(1.57, Frame("Q123")) - self.assertEqual(shift_phase.channel, Frame(123)) + self.assertEqual(shift_phase.channel, Frame("Q123")) class TestSnapshot(QiskitTestCase): diff --git a/test/python/pulse/test_parameters.py b/test/python/pulse/test_parameters.py index f3e0e5ba18c1..098c188da382 100644 --- a/test/python/pulse/test_parameters.py +++ b/test/python/pulse/test_parameters.py @@ -451,10 +451,10 @@ def test_frames(self): dur = Parameter("duration") phase = Parameter("phase") - signal = Signal(pulse.Gaussian(dur, 0.1, 40), Frame(f_param)) + signal = Signal(pulse.Gaussian(dur, 0.1, 40), Frame("Q", f_param)) sched = pulse.ScheduleBlock() sched.append(pulse.Play(signal, DriveChannel(ch_param))) - sched.append(pulse.ShiftPhase(phase, Frame(f_param))) + sched.append(pulse.ShiftPhase(phase, Frame("Q", f_param))) sched.append(pulse.Play(signal, DriveChannel(ch_param))) for param in [ch_param, f_param, dur, phase]: diff --git a/test/python/pulse/test_pulse_lib.py b/test/python/pulse/test_pulse_lib.py index 495047d8e8ba..38bf3e752a8c 100644 --- a/test/python/pulse/test_pulse_lib.py +++ b/test/python/pulse/test_pulse_lib.py @@ -117,9 +117,9 @@ class TestSignal(QiskitTestCase): def test_construction(self): """Test the basics of the signal class.""" - signal = Signal(Gaussian(160, 0.1, 40), Frame(123)) + signal = Signal(Gaussian(160, 0.1, 40), Frame("Q1")) self.assertEqual(signal.pulse, Gaussian(160, 0.1, 40)) - self.assertEqual(signal.frame, Frame(123)) + self.assertEqual(signal.frame, Frame("Q1")) class TestParametricPulses(QiskitTestCase): From 75cfac746e05e37708347c5cf3bdae3a23322809 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 14 Jun 2021 16:50:14 +0200 Subject: [PATCH 072/111] * Black. --- qiskit/providers/models/pulsedefaults.py | 1 - qiskit/pulse/transforms/frames.py | 4 +++- test/python/pulse/test_frames.py | 17 ++++++++++------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index 3df82a2f0894..e9da354b62b8 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -290,7 +290,6 @@ def frames(self) -> Dict[Frame, Dict]: for qubit, freq in enumerate(self.qubit_freq_est): frames[Frame(f"Q{qubit}")] = {"frequency": freq, "purpose": f"Frame of qubit {qubit}"} - for meas, freq in enumerate(self.meas_freq_est): frames[Frame(f"M{meas}")] = {"frequency": freq, "purpose": f"Frame of meas {meas}"} diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index dfa888426b89..94ad2b2b0c86 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -23,7 +23,9 @@ from qiskit.pulse.frame import Frame -def resolve_frames(schedule: Union[Schedule, ScheduleBlock], frames_config: Dict[Frame, Dict]) -> Schedule: +def resolve_frames( + schedule: Union[Schedule, ScheduleBlock], frames_config: Dict[Frame, Dict] +) -> Schedule: """ Parse the schedule and replace instructions on Frames by instructions on the appropriate channels. diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 6537f35ce603..9c5339a360bf 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -94,13 +94,13 @@ def test_phase_advance(self): pulse.Frame("Q0"): { "frequency": self.freq0, "purpose": "Frame of qubit 0.", - "sample_duration": self.dt_ + "sample_duration": self.dt_, }, pulse.Frame("Q1"): { "frequency": self.freq1, "purpose": "Frame of qubit 1.", - "sample_duration": self.dt_ - } + "sample_duration": self.dt_, + }, } frame0_ = ResolvedFrame(pulse.Frame("Q0"), self.freq0, self.dt_) @@ -126,7 +126,9 @@ def test_phase_advance(self): channel_phase = 0.0 for time, delta, frame_freq, other_freq, index in params: - wanted_phase = np.angle(np.exp(2.0j * np.pi * frame_freq * time * self.dt_)) % (2 * np.pi) + wanted_phase = np.angle(np.exp(2.0j * np.pi * frame_freq * time * self.dt_)) % ( + 2 * np.pi + ) phase_diff = (wanted_phase - channel_phase) % (2 * np.pi) @@ -135,11 +137,12 @@ def test_phase_advance(self): else: self.assertTrue(isinstance(resolved[index][1], pulse.ShiftPhase)) - self.assertEqual(resolved[index][0], time) self.assertAlmostEqual(resolved[index][1].phase % (2 * np.pi), phase_diff, places=8) - channel_phase += np.angle(np.exp(2.0j * np.pi * frame_freq * delta * self.dt_)) % (2 * np.pi) + channel_phase += np.angle(np.exp(2.0j * np.pi * frame_freq * delta * self.dt_)) % ( + 2 * np.pi + ) def test_phase_advance_with_instructions(self): """Test that the phase advances are properly computed with frame instructions.""" @@ -196,7 +199,7 @@ def test_broadcasting(self): pulse.Frame("Q0"): { "frequency": self.freq0, "purpose": "Frame of qubit 0.", - "sample_duration": self.dt_ + "sample_duration": self.dt_, } } From edf2510986f18697aab411e431776b0061d28c0b Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 14 Jun 2021 18:19:52 +0200 Subject: [PATCH 073/111] * Caught an edge case. --- qiskit/pulse/transforms/frames.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 94ad2b2b0c86..cdb125780142 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -46,7 +46,7 @@ def resolve_frames( if isinstance(schedule, ScheduleBlock): schedule = block_to_schedule(schedule) - if frames_config is None: + if frames_config is None or not frames_config: return schedule resolved_frames = {} From b736a91b9af9cdd362a2c1cb03c5dd6caa0dc745 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 14 Jun 2021 19:09:43 +0200 Subject: [PATCH 074/111] * Removed a bug that came from a merge issue. --- qiskit/assembler/assemble_schedules.py | 9 +-------- qiskit/pulse/transforms/frames.py | 6 +++--- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index c2ba5b9fe3c2..6d4310a9182e 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -100,14 +100,7 @@ def _assemble_experiments( ) instruction_converter = instruction_converter(qobj.PulseQobjInstruction, **run_config.to_dict()) - formatted_schedules = [] - for sched in schedules: - if isinstance(sched, pulse.Schedule): - sched = transforms.inline_subroutines(sched) - sched = transforms.flatten(sched) - formatted_schedules.append(sched) - else: - formatted_schedules.append(pulse.Schedule(sched)) + formatted_schedules = [transforms.target_qobj_transform(sched) for sched in schedules] frames_config = getattr(run_config, "frames_config", None) resolved_schedules = [ diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index cdb125780142..46140486e3a4 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -43,12 +43,12 @@ def resolve_frames( Raises: PulseError: if a frame is not configured. """ - if isinstance(schedule, ScheduleBlock): - schedule = block_to_schedule(schedule) - if frames_config is None or not frames_config: return schedule + if isinstance(schedule, ScheduleBlock): + schedule = block_to_schedule(schedule) + resolved_frames = {} sample_duration = None for frame, settings in frames_config.items(): From f3fbbd87cf0456f609118df547a615fc4220de46 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 14 Jun 2021 21:08:32 +0200 Subject: [PATCH 075/111] * Lint. --- qiskit/pulse/frame.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 59fd07761f4f..d96c43c7b1c9 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -27,6 +27,9 @@ def __init__(self, identifier: str, parametric_index: Optional[Parameter] = None Args: identifier: The index of the frame. parametric_index: An optional parameter to specify the numeric part of the index. + + Raises: + PulseError: if the frame identifier is not a string. """ if parametric_index is not None: validate_index(parametric_index) From 7b2b25f1d901c736682f77a018764e101c563663 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 15 Jun 2021 17:33:00 +0200 Subject: [PATCH 076/111] * Removed unused variable in test. --- test/python/pulse/test_frames.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 9c5339a360bf..476c5e7816be 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -118,14 +118,14 @@ def test_phase_advance(self): resolved = resolve_frames(sched, frames_config).instructions params = [ - (0, 160, self.freq0, self.freq0, 1), - (160, 160, self.freq1, self.freq0, 4), - (320, 160, self.freq0, self.freq1, 7), - (480, 160, self.freq1, self.freq0, 10), + (0, 160, self.freq0, 1), + (160, 160, self.freq1, 4), + (320, 160, self.freq0, 7), + (480, 160, self.freq1, 10), ] channel_phase = 0.0 - for time, delta, frame_freq, other_freq, index in params: + for time, delta, frame_freq, index in params: wanted_phase = np.angle(np.exp(2.0j * np.pi * frame_freq * time * self.dt_)) % ( 2 * np.pi ) From bff077f95aa64b5d96e814b8df788fcc74fe693c Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 22 Jun 2021 10:53:47 +0200 Subject: [PATCH 077/111] * Added a data class in resolved_frame-py. --- qiskit/pulse/transforms/resolved_frame.py | 49 ++++++++++++++--------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 0f9f4a65bfe6..08f3e3bca441 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -14,6 +14,7 @@ from abc import ABC from typing import Optional +from dataclasses import dataclass import numpy as np from qiskit.circuit.parameterexpression import ParameterExpression @@ -25,6 +26,14 @@ from qiskit.pulse.exceptions import PulseError +@dataclass +class TimeFrequencyPhase: + """Class to help keep track of time, frequency and phase.""" + time: float + frequency: float + phase: float + + class Tracker(ABC): """ Implements a class to keep track of the phase and frequency of a frame @@ -38,7 +47,7 @@ def __init__(self, identifier: str, sample_duration: float): sample_duration: Duration of a sample. """ self._identifier = identifier - self._frequencies_phases = [] # List of (time, frequency, phase) tuples + self._frequencies_phases = [] # List of TimeFreqPhase instances self._instructions = {} self._sample_duration = sample_duration @@ -57,10 +66,10 @@ def frequency(self, time: int) -> float: Returns: frequency: The frequency of the frame right before time. """ - frequency = self._frequencies_phases[0][1] - for time_freq in self._frequencies_phases: - if time_freq[0] <= time: - frequency = time_freq[1] + frequency = self._frequencies_phases[0].frequency + for tfp in self._frequencies_phases: + if tfp.time <= time: + frequency = tfp.frequency else: break @@ -79,13 +88,13 @@ def phase(self, time: int) -> float: if len(self._frequencies_phases) == 0: return 0.0 - phase = self._frequencies_phases[0][1] - last_time = self._frequencies_phases[0][0] + phase = self._frequencies_phases[0].phase + last_time = self._frequencies_phases[0].time - for time_freq in self._frequencies_phases: - if time_freq[0] <= time: - phase = time_freq[2] - last_time = time_freq[0] + for tfp in self._frequencies_phases: + if tfp.time <= time: + phase = tfp.phase + last_time = tfp.time else: break @@ -101,28 +110,32 @@ def set_frequency(self, time: int, frequency: float): frequency: The frequency to which self is set after the given time. """ insert_idx = 0 - for idx, time_freq in enumerate(self._frequencies_phases): - if time_freq[0] < time: + for idx, tfp in enumerate(self._frequencies_phases): + if tfp.time < time: insert_idx = idx else: break phase = self.phase(time) - self._frequencies_phases.insert(insert_idx + 1, (time, frequency, phase)) + new_tfp = TimeFrequencyPhase(time=time, frequency=frequency, phase=phase) + + self._frequencies_phases.insert(insert_idx + 1, new_tfp) def set_phase(self, time: int, phase: float): """Insert a new phase in the time-ordered phases.""" insert_idx = 0 - for idx, time_freq in enumerate(self._frequencies_phases): - if time_freq[0] < time: + for idx, tfp in enumerate(self._frequencies_phases): + if tfp.time < time: insert_idx = idx else: break frequency = self.frequency(time) - self._frequencies_phases.insert(insert_idx + 1, (time, frequency, phase)) + new_tfp = TimeFrequencyPhase(time=time, frequency=frequency, phase=phase) + + self._frequencies_phases.insert(insert_idx + 1, new_tfp) class ResolvedFrame(Tracker): @@ -154,7 +167,7 @@ def __init__( raise PulseError("A parameterized frame cannot initialize a ResolvedFrame.") super().__init__(frame.name, sample_duration) - self._frequencies_phases = [(0, frequency, phase)] + self._frequencies_phases = [TimeFrequencyPhase(time=0, frequency=frequency, phase=phase)] self._purpose = purpose @property From d51d2d074caecc9c63401a278af3b8bf156ba269 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 22 Jun 2021 11:13:20 +0200 Subject: [PATCH 078/111] * Added property Signal to Play. * Changed property pulse in Play to return only Pulse. --- qiskit/pulse/instructions/play.py | 13 ++++++++++++- qiskit/pulse/library/signal.py | 2 +- qiskit/pulse/transforms/frames.py | 4 ++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/qiskit/pulse/instructions/play.py b/qiskit/pulse/instructions/play.py index 0e95986a377e..974c7795df0c 100644 --- a/qiskit/pulse/instructions/play.py +++ b/qiskit/pulse/instructions/play.py @@ -62,8 +62,19 @@ def __init__( super().__init__(operands=(pulse, channel), name=name) @property - def pulse(self) -> [Pulse, Signal]: + def pulse(self) -> Pulse: """A description of the samples that will be played.""" + if isinstance(self.operands[0], Signal): + return self.operands[0].pulse + + return self.operands[0] + + @property + def signal(self) -> Signal: + """The signal that will be played.""" + if isinstance(self.operands[0], Pulse): + return Signal(self.operands[0], None) + return self.operands[0] @property diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index 2f43015cd926..87a3595f7bba 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -26,7 +26,7 @@ class Signal: Frame, i.e. a frequency and a phase. """ - def __init__(self, pulse: Pulse, frame: Frame, name: Optional[str] = None): + def __init__(self, pulse: Pulse, frame: Optional[Frame], name: Optional[str] = None): """ Args: pulse: The envelope of the signal. diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 46140486e3a4..22a36a55c8f8 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -74,8 +74,8 @@ def resolve_frames( if isinstance(inst, instructions.Play): chan = inst.channel - if isinstance(inst.pulse, Signal): - frame = inst.pulse.frame + if inst.signal.frame is not None: + frame = inst.signal.frame if frame not in resolved_frames: raise PulseError(f"{frame} is not configured and cannot " f"be resolved.") From 31ef6050d3380fc730f567b4917ac5f5a16a5ca9 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 22 Jun 2021 11:21:09 +0200 Subject: [PATCH 079/111] * Improved set_phase and set_frequency in resolved_frame.py. --- qiskit/pulse/transforms/resolved_frame.py | 33 +++++++---------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 08f3e3bca441..33124151b859 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -109,33 +109,18 @@ def set_frequency(self, time: int, frequency: float): time: The time in samples (i.e. measured in units of dt). frequency: The frequency to which self is set after the given time. """ - insert_idx = 0 - for idx, tfp in enumerate(self._frequencies_phases): - if tfp.time < time: - insert_idx = idx - else: - break - - phase = self.phase(time) - - new_tfp = TimeFrequencyPhase(time=time, frequency=frequency, phase=phase) - - self._frequencies_phases.insert(insert_idx + 1, new_tfp) + new_tfp = TimeFrequencyPhase(time=time, frequency=frequency, phase=self.phase(time)) + self._frequencies_phases = sorted(self._frequencies_phases + [new_tfp], key=lambda x: x[0]) def set_phase(self, time: int, phase: float): - """Insert a new phase in the time-ordered phases.""" - insert_idx = 0 - for idx, tfp in enumerate(self._frequencies_phases): - if tfp.time < time: - insert_idx = idx - else: - break - - frequency = self.frequency(time) - - new_tfp = TimeFrequencyPhase(time=time, frequency=frequency, phase=phase) + """Insert a new phase in the time-ordered phases. - self._frequencies_phases.insert(insert_idx + 1, new_tfp) + Args: + time: The time in samples (i.e. measured in units of dt). + phase: The phase to which self is set after the given time. + """ + new_tfp = TimeFrequencyPhase(time=time, frequency=self.frequency(time), phase=phase) + self._frequencies_phases = sorted(self._frequencies_phases + [new_tfp], key=lambda x: x[0]) class ResolvedFrame(Tracker): From 59d2d99e661f683f001920e718f9180451ed0cdf Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 22 Jun 2021 11:24:25 +0200 Subject: [PATCH 080/111] * Minor bug fix. --- qiskit/pulse/transforms/resolved_frame.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 33124151b859..5ddb4b871b72 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -109,8 +109,8 @@ def set_frequency(self, time: int, frequency: float): time: The time in samples (i.e. measured in units of dt). frequency: The frequency to which self is set after the given time. """ - new_tfp = TimeFrequencyPhase(time=time, frequency=frequency, phase=self.phase(time)) - self._frequencies_phases = sorted(self._frequencies_phases + [new_tfp], key=lambda x: x[0]) + tfp = TimeFrequencyPhase(time=time, frequency=frequency, phase=self.phase(time)) + self._frequencies_phases = sorted(self._frequencies_phases + [tfp], key=lambda x: x.time) def set_phase(self, time: int, phase: float): """Insert a new phase in the time-ordered phases. @@ -119,8 +119,8 @@ def set_phase(self, time: int, phase: float): time: The time in samples (i.e. measured in units of dt). phase: The phase to which self is set after the given time. """ - new_tfp = TimeFrequencyPhase(time=time, frequency=self.frequency(time), phase=phase) - self._frequencies_phases = sorted(self._frequencies_phases + [new_tfp], key=lambda x: x[0]) + tfp = TimeFrequencyPhase(time=time, frequency=self.frequency(time), phase=phase) + self._frequencies_phases = sorted(self._frequencies_phases + [tfp], key=lambda x: x.time) class ResolvedFrame(Tracker): From b7e8b4026d2ee2308ec47d3575b0dcfa9384137f Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 22 Jun 2021 11:45:30 +0200 Subject: [PATCH 081/111] * Resolved frame now sorts in reversed order. * Added corresponding tests. --- qiskit/pulse/transforms/resolved_frame.py | 20 ++++++++++---------- test/python/pulse/test_frames.py | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 5ddb4b871b72..3ac0b0382330 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -66,11 +66,11 @@ def frequency(self, time: int) -> float: Returns: frequency: The frequency of the frame right before time. """ - frequency = self._frequencies_phases[0].frequency - for tfp in self._frequencies_phases: + frequency = self._frequencies_phases[-1].frequency + for tfp in reversed(self._frequencies_phases): + frequency = tfp.frequency + if tfp.time <= time: - frequency = tfp.frequency - else: break return frequency @@ -88,14 +88,14 @@ def phase(self, time: int) -> float: if len(self._frequencies_phases) == 0: return 0.0 - phase = self._frequencies_phases[0].phase - last_time = self._frequencies_phases[0].time + phase = self._frequencies_phases[-1].phase + last_time = self._frequencies_phases[-1].time + + for tfp in reversed(self._frequencies_phases): + phase = tfp.phase + last_time = tfp.time - for tfp in self._frequencies_phases: if tfp.time <= time: - phase = tfp.phase - last_time = tfp.time - else: break freq = self.frequency(time) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 476c5e7816be..b3923332d199 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -73,6 +73,29 @@ def test_phase(self): phase = np.angle(np.exp(two_pi_dt * (5.5e9 * 99 + 5.2e9 * (time - 99)))) % (2 * np.pi) self.assertAlmostEqual(r_frame.phase(time), phase, places=8) + def test_get_phase(self): + """Test that we get the correct phase as function of time.""" + + r_frame = ResolvedFrame(pulse.Frame("Q0"), 0.0, self.dt_) + r_frame.set_phase(4, 1.0) + r_frame.set_phase(8, 2.0) + + expected = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2] + for time, phase in enumerate(expected): + self.assertEqual(r_frame.phase(time), phase) + + def test_get_frequency(self): + """Test that we get the correct phase as function of time.""" + + r_frame = ResolvedFrame(pulse.Frame("Q0"), 1.0, self.dt_) + r_frame.set_frequency(4, 2.0) + r_frame.set_frequency(8, 3.0) + + expected = [1, 1, 1, 1, 2, 2, 2, 2, 3, 3] + for time, phase in enumerate(expected): + self.assertEqual(r_frame.frequency(time), phase) + + def test_phase_advance(self): """Test that phases are properly set when frames are resolved. From 252e1fbb51a565d4f10d563d1e998e1ce898c8c5 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 22 Jun 2021 13:53:30 +0200 Subject: [PATCH 082/111] * Added a FramesConfiguration class. --- qiskit/compiler/assembler.py | 21 +++---- qiskit/providers/models/pulsedefaults.py | 6 +- qiskit/pulse/frame.py | 80 +++++++++++++++++++++++- qiskit/pulse/transforms/frames.py | 21 ++++--- test/python/pulse/test_frames.py | 64 +++++++++++++++++-- 5 files changed, 164 insertions(+), 28 deletions(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index bf9af98c2ee8..a8c46fe4bbe0 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -27,7 +27,7 @@ from qiskit.pulse import LoConfig, Instruction from qiskit.pulse import Schedule, ScheduleBlock from qiskit.pulse.channels import PulseChannel -from qiskit.pulse.frame import Frame +from qiskit.pulse.frame import FramesConfiguration from qiskit.qobj import QobjHeader, Qobj from qiskit.qobj.utils import MeasLevel, MeasReturnType from qiskit.validation.jsonschema import SchemaValidationError @@ -76,7 +76,7 @@ def assemble( parameter_binds: Optional[List[Dict[Parameter, float]]] = None, parametric_pulses: Optional[List[str]] = None, init_qubits: bool = True, - frames_config: Dict[Frame, Dict] = None, + frames_config: FramesConfiguration = None, use_measure_esp: Optional[bool] = None, **run_config: Dict, ) -> Qobj: @@ -144,11 +144,8 @@ def assemble( ['gaussian', 'constant'] init_qubits: Whether to reset the qubits to the ground state for each shot. Default: ``True``. - frames_config: Dictionary of user provided frames configuration. The key is the frame - and the value is a dictionary with the configuration of the frame which must be of - the form {'frequency': float, 'purpose': str, 'sample_duration': float}. This - object is be used to initialize ResolvedFrame instance to resolve the frames in - the Schedule. + frames_config: An instance of FramesConfiguration defining how the frames are configured + for the backend. use_measure_esp: Whether to use excited state promoted (ESP) readout for the final measurement in each circuit. ESP readout discriminates between the ``|0>`` and higher transmon states to improve readout fidelity. See @@ -459,12 +456,14 @@ def _parse_pulse_args( meas_map = meas_map or getattr(backend_config, "meas_map", None) - frames_config_ = {} + frames_config_ = FramesConfiguration() if backend: - frames_config_ = getattr(backend.defaults(), "frames", {}) + frames_config_ = getattr(backend.defaults(), "frames", FramesConfiguration()) - for config in frames_config_.values(): - config["sample_duration"] = backend_config.dt + # The frames in pulse defaults do not provide a dt (needed to compute phase + # advances) so we add it here. + for frame_def in frames_config_.definitions: + frame_def.sample_duration = backend_config.dt if frames_config is None: frames_config = frames_config_ diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index 29cb62cd75f0..3bde7b61e743 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -19,7 +19,7 @@ from qiskit.pulse.schedule import Schedule from qiskit.qobj import PulseLibraryItem, PulseQobjInstruction from qiskit.qobj.converters import QobjToInstructionConverter -from qiskit.pulse.frame import Frame +from qiskit.pulse.frame import Frame, FramesConfiguration class MeasurementKernel: @@ -279,7 +279,7 @@ def from_dict(cls, data): return cls(**in_data) @property - def frames(self) -> Dict[Frame, Dict]: + def frames(self) -> FramesConfiguration: """Get the frames supported by the backend. Returns: @@ -292,7 +292,7 @@ def frames(self) -> Dict[Frame, Dict]: for meas, freq in enumerate(self.meas_freq_est): frames[Frame(f"M{meas}")] = {"frequency": freq, "purpose": f"Frame of meas {meas}"} - return frames + return FramesConfiguration.from_dict(frames) def __str__(self): qubit_freqs = [freq / 1e9 for freq in self.qubit_freq_est] diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index d96c43c7b1c9..cbe22b8cf887 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -12,7 +12,8 @@ """Implements a Frame.""" -from typing import Optional, Tuple +from dataclasses import dataclass +from typing import Dict, List, Optional, Tuple from qiskit.circuit import Parameter from qiskit.pulse.utils import validate_index @@ -75,3 +76,80 @@ def __eq__(self, other: "Frame") -> bool: def __hash__(self): return self._hash + + +@dataclass +class FrameDefinition: + """A class to keep track of frame definitions.""" + + # The frequency of the frame. + frequency: float + + # The duration of the samples in the control electronics. + sample_duration: float + + # A user-friendly string defining the purpose of the Frame. + purpose: str + + +class FramesConfiguration: + """A class that specifies how frames on a backend are configured.""" + + def __init__(self): + """Initialize the frames configuration.""" + self._frames = dict() + + @classmethod + def from_dict(cls, frames_config: Dict[Frame, Dict]) -> "FramesConfiguration": + """Create a frame configuration from a dict.""" + config = FramesConfiguration() + + for frame, definition in frames_config.items(): + config.add_frame(frame, **definition) + + return config + + def to_dict(self) -> Dict: + """Export the frames configuration to a dictionary""" + config = dict() + + for frame, definition in self._frames.items(): + config[frame] = definition.__dict__ + + return config + + def add_frame( + self, + frame: Frame, + frequency: float, + sample_duration: Optional[float] = None, + purpose: Optional[str] = None + ): + """Add a frame to the frame configuration. + + Args: + frame: The frame instance to add. + frequency: The frequency of the frame. + sample_duration: The sample duration. + purpose: A string describing the purpose of the frame. + """ + self._frames[frame] = FrameDefinition( + frequency=frequency, sample_duration=sample_duration, purpose=purpose + ) + + @property + def definitions(self) -> List[FrameDefinition]: + """Return the definitions for each frame.""" + return [frame_def for frame_def in self._frames.values()] + + def items(self): + """Return the items in the frames config.""" + return self._frames.items() + + def __getitem__(self, frame: Frame) -> FrameDefinition: + """Return the frame definition.""" + return self._frames[frame] + + def __setitem__(self, frame: Frame, frame_def: FrameDefinition): + """Return the frame definition.""" + self._frames[frame] = frame_def diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 22a36a55c8f8..116cc1fcee6b 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -12,19 +12,18 @@ """Replace a schedule with frames by one with instructions on PulseChannels only.""" -from typing import Dict, Union +from typing import Union from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.pulse.transforms.resolved_frame import ResolvedFrame, ChannelTracker from qiskit.pulse.transforms.canonicalization import block_to_schedule -from qiskit.pulse.library import Signal from qiskit.pulse.exceptions import PulseError from qiskit.pulse import channels as chans, instructions -from qiskit.pulse.frame import Frame +from qiskit.pulse.frame import FramesConfiguration def resolve_frames( - schedule: Union[Schedule, ScheduleBlock], frames_config: Dict[Frame, Dict] + schedule: Union[Schedule, ScheduleBlock], frames_config: FramesConfiguration ) -> Schedule: """ Parse the schedule and replace instructions on Frames by instructions on the @@ -33,8 +32,7 @@ def resolve_frames( Args: schedule: The schedule for which to replace frames with the appropriate channels. - frames_config: A dictionary with the frame index as key and the values are - a dict which can be used to initialized a ResolvedFrame. + frames_config: An instance of FramesConfiguration defining the frames. Returns: new_schedule: A new schedule where frames have been replaced with @@ -51,13 +49,18 @@ def resolve_frames( resolved_frames = {} sample_duration = None - for frame, settings in frames_config.items(): - resolved_frame = ResolvedFrame(frame, **settings) + for frame, frame_def in frames_config.items(): + resolved_frame = ResolvedFrame( + frame, + frequency=frame_def.frequency, + sample_duration=frame_def.sample_duration, + purpose=frame_def.purpose, + ) # Extract shift and set frame operations from the schedule. resolved_frame.set_frame_instructions(schedule) resolved_frames[frame] = resolved_frame - sample_duration = settings["sample_duration"] + sample_duration = frame_def.sample_duration if sample_duration is None: raise PulseError("Frame configuration does not have a sample duration.") diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index b3923332d199..5e17ae20770b 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -20,6 +20,7 @@ from qiskit.pulse.transforms import resolve_frames, block_to_schedule from qiskit.pulse.transforms.resolved_frame import ResolvedFrame from qiskit.pulse.parameter_manager import ParameterManager +from qiskit.pulse.frame import Frame, FramesConfiguration class TestFrame(QiskitTestCase): @@ -48,6 +49,61 @@ def test_parameter(self): self.assertEqual(frame, pulse.Frame("Q", param)) +class TestFramesConfiguration(QiskitTestCase): + """The the frames config object.""" + + def test_frame_config(self): + """Test that frame configs can be properly created.""" + + config = { + Frame("Q0"): { + "frequency": 5.5e9, + "sample_duration": 0.222e-9, + "purpose": "Frame of qubit 0" + }, + Frame("Q1"): { + "frequency": 5.2e9, + "sample_duration": 0.222e-9, + }, + Frame("Q2"): { + "frequency": 5.2e9, + } + } + + frames_config = FramesConfiguration.from_dict(config) + + self.assertEqual(frames_config[Frame("Q0")].frequency, 5.5e9) + self.assertEqual(frames_config[Frame("Q0")].sample_duration, 0.222e-9) + self.assertEqual(frames_config[Frame("Q1")].frequency, 5.2e9) + self.assertTrue(frames_config[Frame("Q2")].sample_duration is None) + + for frame_def in frames_config.definitions: + frame_def.sample_duration = 0.1e-9 + + for name in ["Q0", "Q1", "Q2"]: + self.assertEqual(frames_config[Frame(name)].sample_duration, 0.1e-9) + + + def test_merge_two_configs(self): + """Test to see if we can merge two configs.""" + + config1 = FramesConfiguration.from_dict({ + Frame("Q0"): {"frequency": 5.5e9, "sample_duration": 0.222e-9}, + Frame("Q1"): {"frequency": 5.2e9, "sample_duration": 0.222e-9}, + }) + + config2 = FramesConfiguration.from_dict({ + Frame("Q1"): {"frequency": 4.5e9, "sample_duration": 0.222e-9}, + Frame("Q2"): {"frequency": 4.2e9, "sample_duration": 0.222e-9}, + }) + + for frame, frame_def in config2.items(): + config1[frame] = frame_def + + for name, freq in [("Q0", 5.5e9), ("Q1", 4.5e9), ("Q2", 4.2e9)]: + self.assertEqual(config1[Frame(name)].frequency, freq) + + class TestResolvedFrames(QiskitTestCase): """Test that resolved frames behave properly.""" @@ -113,7 +169,7 @@ def test_phase_advance(self): pulse.play(sig0, d0) pulse.play(sig1, d0) - frames_config = { + frames_config = FramesConfiguration.from_dict({ pulse.Frame("Q0"): { "frequency": self.freq0, "purpose": "Frame of qubit 0.", @@ -124,7 +180,7 @@ def test_phase_advance(self): "purpose": "Frame of qubit 1.", "sample_duration": self.dt_, }, - } + }) frame0_ = ResolvedFrame(pulse.Frame("Q0"), self.freq0, self.dt_) frame1_ = ResolvedFrame(pulse.Frame("Q1"), self.freq1, self.dt_) @@ -218,13 +274,13 @@ def test_broadcasting(self): pulse.play(sig, pulse.DriveChannel(3)) pulse.play(sig, pulse.ControlChannel(0)) - frames_config = { + frames_config = FramesConfiguration.from_dict({ pulse.Frame("Q0"): { "frequency": self.freq0, "purpose": "Frame of qubit 0.", "sample_duration": self.dt_, } - } + }) resolved = resolve_frames(sched, frames_config).instructions From c9afa4733f38d07b6b9b10f6041bb79089b552b9 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 22 Jun 2021 14:04:44 +0200 Subject: [PATCH 083/111] * Tighter integration between FrameDefintion, FramesConfig, and ResolvedFrame. --- qiskit/pulse/frame.py | 14 ++++++++--- qiskit/pulse/transforms/frames.py | 10 ++------ qiskit/pulse/transforms/resolved_frame.py | 30 ++++++++++------------- test/python/pulse/test_frames.py | 17 +++++++------ 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index cbe22b8cf887..71123e557e58 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -82,14 +82,17 @@ def __hash__(self): class FrameDefinition: """A class to keep track of frame definitions.""" - # The frequency of the frame. + # The frequency of the frame at time zero. frequency: float # The duration of the samples in the control electronics. sample_duration: float # A user-friendly string defining the purpose of the Frame. - purpose: str + purpose: str = None + + # The phase of the frame at time zero. + phase: float = 0.0 class FramesConfiguration: @@ -101,7 +104,12 @@ def __init__(self): @classmethod def from_dict(cls, frames_config: Dict[Frame, Dict]) -> "FramesConfiguration": - """Create a frame configuration from a dict.""" + """Create a frame configuration from a dict. + + This dict must have frames as keys and a dict as value. This dict is then + used to instantiate a FramesDefinition instance. Thus refer to FrameDefinition, + to see what key-value pairs are needed. + """ config = FramesConfiguration() for frame, definition in frames_config.items(): diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 116cc1fcee6b..d53c675b8279 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -50,16 +50,10 @@ def resolve_frames( resolved_frames = {} sample_duration = None for frame, frame_def in frames_config.items(): - resolved_frame = ResolvedFrame( - frame, - frequency=frame_def.frequency, - sample_duration=frame_def.sample_duration, - purpose=frame_def.purpose, - ) + resolved_frames[frame] = ResolvedFrame(frame, frame_def) # Extract shift and set frame operations from the schedule. - resolved_frame.set_frame_instructions(schedule) - resolved_frames[frame] = resolved_frame + resolved_frames[frame].set_frame_instructions(schedule) sample_duration = frame_def.sample_duration if sample_duration is None: diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 3ac0b0382330..dfc31bdb7cb8 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -19,7 +19,7 @@ from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.pulse.channels import PulseChannel -from qiskit.pulse.frame import Frame +from qiskit.pulse.frame import Frame, FrameDefinition from qiskit.pulse.schedule import Schedule from qiskit.pulse.instructions.frequency import SetFrequency, ShiftFrequency from qiskit.pulse.instructions.phase import SetPhase, ShiftPhase @@ -129,21 +129,13 @@ class ResolvedFrame(Tracker): Frame at any given point in time. """ - def __init__( - self, - frame: Frame, - frequency: float, - sample_duration: float, - phase: float = 0.0, - purpose: Optional[str] = None, - ): - """ + def __init__(self, frame: Frame, definition: FrameDefinition): + """Initialized a resolved frame. + Args: frame: The frame to track. - frequency: The initial frequency of the frame. - sample_duration: Duration of a sample. - phase: The initial phase of the frame. - purpose: A human readable description of the frame. + definition: An instance of the FrameDefinition dataclass which defines + the frequency, phase, sample_duration, and purpose of a frame. Raises: PulseError: If there are still parameters in the given frame. @@ -151,9 +143,13 @@ def __init__( if isinstance(frame.identifier[1], ParameterExpression): raise PulseError("A parameterized frame cannot initialize a ResolvedFrame.") - super().__init__(frame.name, sample_duration) - self._frequencies_phases = [TimeFrequencyPhase(time=0, frequency=frequency, phase=phase)] - self._purpose = purpose + super().__init__(frame.name, definition.sample_duration) + + self._frequencies_phases = [ + TimeFrequencyPhase(time=0, frequency=definition.frequency, phase=definition.phase) + ] + + self._purpose = definition.purpose @property def purpose(self) -> str: diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 5e17ae20770b..11a931fa47fa 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -20,7 +20,7 @@ from qiskit.pulse.transforms import resolve_frames, block_to_schedule from qiskit.pulse.transforms.resolved_frame import ResolvedFrame from qiskit.pulse.parameter_manager import ParameterManager -from qiskit.pulse.frame import Frame, FramesConfiguration +from qiskit.pulse.frame import Frame, FramesConfiguration, FrameDefinition class TestFrame(QiskitTestCase): @@ -82,6 +82,7 @@ def test_frame_config(self): for name in ["Q0", "Q1", "Q2"]: self.assertEqual(frames_config[Frame(name)].sample_duration, 0.1e-9) + self.assertEqual(frames_config[Frame(name)].phase, 0.0) def test_merge_two_configs(self): @@ -117,7 +118,7 @@ def test_phase(self): """Test the phase of the resolved frames.""" two_pi_dt = 2.0j * np.pi * self.dt_ - r_frame = ResolvedFrame(pulse.Frame("Q0"), 5.5e9, self.dt_) + r_frame = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(5.5e9, self.dt_)) r_frame.set_frequency(99, 5.2e9) for time in [0, 55, 98]: @@ -132,7 +133,7 @@ def test_phase(self): def test_get_phase(self): """Test that we get the correct phase as function of time.""" - r_frame = ResolvedFrame(pulse.Frame("Q0"), 0.0, self.dt_) + r_frame = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(0.0, self.dt_)) r_frame.set_phase(4, 1.0) r_frame.set_phase(8, 2.0) @@ -143,7 +144,7 @@ def test_get_phase(self): def test_get_frequency(self): """Test that we get the correct phase as function of time.""" - r_frame = ResolvedFrame(pulse.Frame("Q0"), 1.0, self.dt_) + r_frame = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(1.0, self.dt_)) r_frame.set_frequency(4, 2.0) r_frame.set_frequency(8, 3.0) @@ -182,8 +183,8 @@ def test_phase_advance(self): }, }) - frame0_ = ResolvedFrame(pulse.Frame("Q0"), self.freq0, self.dt_) - frame1_ = ResolvedFrame(pulse.Frame("Q1"), self.freq1, self.dt_) + frame0_ = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(self.freq0, self.dt_)) + frame1_ = ResolvedFrame(pulse.Frame("Q1"), FrameDefinition(self.freq1, self.dt_)) # Check that the resolved frames are tracking phases properly for time in [0, 160, 320, 480]: @@ -232,7 +233,7 @@ def test_phase_advance_with_instructions(self): pulse.play(sig, pulse.DriveChannel(0)) pulse.shift_phase(1.0, pulse.Frame("Q0")) - frame = ResolvedFrame(pulse.Frame("Q0"), self.freq0, self.dt_) + frame = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(self.freq0, self.dt_)) frame.set_frame_instructions(block_to_schedule(sched)) self.assertAlmostEqual(frame.phase(0), 0.0, places=8) @@ -251,7 +252,7 @@ def test_phase_advance_with_instructions(self): def test_set_frequency(self): """Test setting the frequency of the resolved frame.""" - frame = ResolvedFrame(pulse.Frame("Q0"), self.freq1, self.dt_) + frame = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(self.freq1, self.dt_)) frame.set_frequency(16, self.freq0) frame.set_frequency(10, self.freq1) frame.set_frequency(10, self.freq1) From ff44ff83fab8b1623145474a55df923fbe9640a1 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 22 Jun 2021 16:03:56 +0200 Subject: [PATCH 084/111] * Added a clear distinction between prefix and index in Frame. --- qiskit/providers/models/pulsedefaults.py | 4 +- qiskit/pulse/frame.py | 36 +++++++----- qiskit/pulse/parameter_manager.py | 2 +- qiskit/pulse/transforms/resolved_frame.py | 2 +- test/python/pulse/test_builder.py | 24 ++++---- test/python/pulse/test_frames.py | 70 +++++++++++------------ test/python/pulse/test_instructions.py | 8 +-- test/python/pulse/test_pulse_lib.py | 4 +- 8 files changed, 80 insertions(+), 70 deletions(-) diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index 3bde7b61e743..c67c2f41aa4e 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -287,10 +287,10 @@ def frames(self) -> FramesConfiguration: """ frames = {} for qubit, freq in enumerate(self.qubit_freq_est): - frames[Frame(f"Q{qubit}")] = {"frequency": freq, "purpose": f"Frame of qubit {qubit}"} + frames[Frame("Q", qubit)] = {"frequency": freq, "purpose": f"Frame of qubit {qubit}"} for meas, freq in enumerate(self.meas_freq_est): - frames[Frame(f"M{meas}")] = {"frequency": freq, "purpose": f"Frame of meas {meas}"} + frames[Frame("M", meas)] = {"frequency": freq, "purpose": f"Frame of meas {meas}"} return FramesConfiguration.from_dict(frames) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 71123e557e58..552e329ad719 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -13,7 +13,7 @@ """Implements a Frame.""" from dataclasses import dataclass -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Tuple, Union from qiskit.circuit import Parameter from qiskit.pulse.utils import validate_index @@ -23,22 +23,32 @@ class Frame: """A frame is a frequency and a phase.""" - def __init__(self, identifier: str, parametric_index: Optional[Parameter] = None): + def __init__(self, prefix: str, index: Union[int, Parameter]): """ + Frames are identified using an identifier, such as "Q10". However, parameters + in Qiskit currently do not support strings as valid assignment values. Therefore, + to allow for parametric identifiers in frames we separate the string prefix from + the numeric (and possibly parametric) index. This behaviour may change in the + future if parameters can be assigned string values. + Args: - identifier: The index of the frame. - parametric_index: An optional parameter to specify the numeric part of the index. + prefix: The index of the frame. + index: An optional parameter to specify the numeric part of the index. Raises: - PulseError: if the frame identifier is not a string. + PulseError: if the frame identifier is not a string or if the identifier + contains numbers which should have been specified as the index. """ - if parametric_index is not None: - validate_index(parametric_index) + if index is not None: + validate_index(index) + + if not isinstance(prefix, str): + raise PulseError(f"Frame identifiers must be string. Got {type(prefix)}.") - if not isinstance(identifier, str): - raise PulseError(f"Frame identifiers must be string. Got {type(identifier)}.") + if any(char.isdigit() for char in prefix): + raise PulseError(f"Frame prefixes cannot contain digits. Found {prefix}") - self._identifier = (identifier, parametric_index) + self._identifier = (prefix, index) self._hash = hash((type(self), self._identifier)) @property @@ -54,10 +64,10 @@ def prefix(self) -> str: @property def name(self) -> str: """Return the shorthand alias for this frame, which is based on its type and index.""" - if self._identifier[1] is None: - return f"{self._identifier[0]}" + if isinstance(self._identifier[1], Parameter): + return f"{self._identifier[0]}{self._identifier[1].name}" - return f"{self._identifier[0]}{self._identifier[1].name}" + return f"{self._identifier[0]}{self._identifier[1]}" def __repr__(self): return f"{self.__class__.__name__}({self.name})" diff --git a/qiskit/pulse/parameter_manager.py b/qiskit/pulse/parameter_manager.py index 45ee7cafb434..f30a8c1710b7 100644 --- a/qiskit/pulse/parameter_manager.py +++ b/qiskit/pulse/parameter_manager.py @@ -215,7 +215,7 @@ def visit_Frame(self, node: Frame): if not isinstance(new_index, ParameterExpression): validate_index(new_index) - return node.__class__(node.prefix + str(new_index)) + return node.__class__(node.prefix, new_index) return node diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index dfc31bdb7cb8..6337ed07c970 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -171,7 +171,7 @@ def set_frame_instructions(self, schedule: Schedule): frame_instructions = schedule.filter(instruction_types=frame_instruction_types) for time, inst in frame_instructions.instructions: - if Frame(self.identifier) == inst.operands[1]: + if isinstance(inst.operands[1], Frame) and self.identifier == inst.operands[1].name: if isinstance(inst, ShiftFrequency): self.set_frequency(time, self.frequency(time) + inst.frequency) elif isinstance(inst, SetFrequency): diff --git a/test/python/pulse/test_builder.py b/test/python/pulse/test_builder.py index 41aa47ef4afb..beb732046326 100644 --- a/test/python/pulse/test_builder.py +++ b/test/python/pulse/test_builder.py @@ -1235,31 +1235,31 @@ class TestBuilderFrames(TestBuilder): def test_simple_frame_schedule(self): """Test basic schedule construction with frames.""" - signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q0")) + signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q", 0)) with pulse.build() as sched: with pulse.align_left(): pulse.play(signal, pulse.DriveChannel(0)) - pulse.shift_phase(1.57, pulse.Frame("Q0")) + pulse.shift_phase(1.57, pulse.Frame("Q", 0)) pulse.play(signal, pulse.DriveChannel(0)) play_gaus = pulse.Play(signal, pulse.DriveChannel(0)) self.assertEqual(sched.instructions[0][0], 0) self.assertEqual(sched.instructions[0][1], play_gaus) self.assertEqual(sched.instructions[1][0], 160) - self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame("Q0"))) + self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame("Q", 0))) self.assertEqual(sched.instructions[2][0], 160) self.assertEqual(sched.instructions[2][1], play_gaus) def test_ignore_frames(self): """Test the behavior of ignoring frames in the alignments context.""" - signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q0")) + signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q", 0)) with pulse.build() as sched: with pulse.align_right(ignore_frames=True): pulse.play(signal, pulse.DriveChannel(0)) - pulse.shift_phase(1.57, pulse.Frame("Q0")) - pulse.shift_phase(1.57, pulse.Frame("Q1")) + pulse.shift_phase(1.57, pulse.Frame("Q", 0)) + pulse.shift_phase(1.57, pulse.Frame("Q", 1)) pulse.play(signal, pulse.DriveChannel(0)) play_gaus = pulse.Play(signal, pulse.DriveChannel(0)) @@ -1269,15 +1269,15 @@ def test_ignore_frames(self): self.assertEqual(sched.instructions[1][0], 160) self.assertEqual(sched.instructions[1][1], play_gaus) self.assertEqual(sched.instructions[2][0], 320) - self.assertEqual(sched.instructions[2][1], pulse.ShiftPhase(1.57, pulse.Frame("Q0"))) + self.assertEqual(sched.instructions[2][1], pulse.ShiftPhase(1.57, pulse.Frame("Q", 0))) self.assertEqual(sched.instructions[3][0], 320) - self.assertEqual(sched.instructions[3][1], pulse.ShiftPhase(1.57, pulse.Frame("Q1"))) + self.assertEqual(sched.instructions[3][1], pulse.ShiftPhase(1.57, pulse.Frame("Q", 1))) with pulse.build() as sched: with pulse.align_right(ignore_frames=False): pulse.play(signal, pulse.DriveChannel(0)) - pulse.shift_phase(1.57, pulse.Frame("Q0")) - pulse.shift_phase(1.57, pulse.Frame("Q1")) + pulse.shift_phase(1.57, pulse.Frame("Q", 0)) + pulse.shift_phase(1.57, pulse.Frame("Q", 1)) pulse.play(signal, pulse.DriveChannel(0)) play_gaus = pulse.Play(signal, pulse.DriveChannel(0)) @@ -1285,8 +1285,8 @@ def test_ignore_frames(self): self.assertEqual(sched.instructions[0][0], 0) self.assertEqual(sched.instructions[0][1], play_gaus) self.assertEqual(sched.instructions[1][0], 160) - self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame("Q0"))) + self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame("Q", 0))) self.assertEqual(sched.instructions[2][0], 160) - self.assertEqual(sched.instructions[2][1], pulse.ShiftPhase(1.57, pulse.Frame("Q1"))) + self.assertEqual(sched.instructions[2][1], pulse.ShiftPhase(1.57, pulse.Frame("Q", 1))) self.assertEqual(sched.instructions[3][0], 160) self.assertEqual(sched.instructions[3][1], play_gaus) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 11a931fa47fa..7aef2f02de49 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -29,7 +29,7 @@ class TestFrame(QiskitTestCase): def basic(self): """Test basic functionality of frames.""" - frame = pulse.Frame("Q13") + frame = pulse.Frame("Q", 13) self.assertEqual(frame.identifier, "Q13") self.assertEqual(frame.name, "Q13") @@ -44,8 +44,8 @@ def test_parameter(self): parameter_manager.update_parameter_table(frame) new_frame = parameter_manager.assign_parameters(frame, {param: 123}) - self.assertEqual(new_frame, pulse.Frame("Q123")) - self.assertEqual(new_frame.identifier, ("Q123", None)) + self.assertEqual(new_frame, pulse.Frame("Q", 123)) + self.assertEqual(new_frame.identifier, ("Q", 123)) self.assertEqual(frame, pulse.Frame("Q", param)) @@ -56,53 +56,53 @@ def test_frame_config(self): """Test that frame configs can be properly created.""" config = { - Frame("Q0"): { + Frame("Q", 0): { "frequency": 5.5e9, "sample_duration": 0.222e-9, "purpose": "Frame of qubit 0" }, - Frame("Q1"): { + Frame("Q", 1): { "frequency": 5.2e9, "sample_duration": 0.222e-9, }, - Frame("Q2"): { + Frame("Q", 2): { "frequency": 5.2e9, } } frames_config = FramesConfiguration.from_dict(config) - self.assertEqual(frames_config[Frame("Q0")].frequency, 5.5e9) - self.assertEqual(frames_config[Frame("Q0")].sample_duration, 0.222e-9) - self.assertEqual(frames_config[Frame("Q1")].frequency, 5.2e9) - self.assertTrue(frames_config[Frame("Q2")].sample_duration is None) + self.assertEqual(frames_config[Frame("Q", 0)].frequency, 5.5e9) + self.assertEqual(frames_config[Frame("Q", 0)].sample_duration, 0.222e-9) + self.assertEqual(frames_config[Frame("Q", 1)].frequency, 5.2e9) + self.assertTrue(frames_config[Frame("Q", 2)].sample_duration is None) for frame_def in frames_config.definitions: frame_def.sample_duration = 0.1e-9 - for name in ["Q0", "Q1", "Q2"]: - self.assertEqual(frames_config[Frame(name)].sample_duration, 0.1e-9) - self.assertEqual(frames_config[Frame(name)].phase, 0.0) + for idx in range(3): + self.assertEqual(frames_config[Frame("Q", idx)].sample_duration, 0.1e-9) + self.assertEqual(frames_config[Frame("Q", idx)].phase, 0.0) def test_merge_two_configs(self): """Test to see if we can merge two configs.""" config1 = FramesConfiguration.from_dict({ - Frame("Q0"): {"frequency": 5.5e9, "sample_duration": 0.222e-9}, - Frame("Q1"): {"frequency": 5.2e9, "sample_duration": 0.222e-9}, + Frame("Q", 0): {"frequency": 5.5e9, "sample_duration": 0.222e-9}, + Frame("Q", 1): {"frequency": 5.2e9, "sample_duration": 0.222e-9}, }) config2 = FramesConfiguration.from_dict({ - Frame("Q1"): {"frequency": 4.5e9, "sample_duration": 0.222e-9}, - Frame("Q2"): {"frequency": 4.2e9, "sample_duration": 0.222e-9}, + Frame("Q", 1): {"frequency": 4.5e9, "sample_duration": 0.222e-9}, + Frame("Q", 2): {"frequency": 4.2e9, "sample_duration": 0.222e-9}, }) for frame, frame_def in config2.items(): config1[frame] = frame_def - for name, freq in [("Q0", 5.5e9), ("Q1", 4.5e9), ("Q2", 4.2e9)]: - self.assertEqual(config1[Frame(name)].frequency, freq) + for idx, freq in enumerate([5.5e9, 4.5e9, 4.2e9]): + self.assertEqual(config1[Frame("Q", idx)].frequency, freq) class TestResolvedFrames(QiskitTestCase): @@ -118,7 +118,7 @@ def test_phase(self): """Test the phase of the resolved frames.""" two_pi_dt = 2.0j * np.pi * self.dt_ - r_frame = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(5.5e9, self.dt_)) + r_frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(5.5e9, self.dt_)) r_frame.set_frequency(99, 5.2e9) for time in [0, 55, 98]: @@ -133,7 +133,7 @@ def test_phase(self): def test_get_phase(self): """Test that we get the correct phase as function of time.""" - r_frame = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(0.0, self.dt_)) + r_frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(0.0, self.dt_)) r_frame.set_phase(4, 1.0) r_frame.set_phase(8, 2.0) @@ -144,7 +144,7 @@ def test_get_phase(self): def test_get_frequency(self): """Test that we get the correct phase as function of time.""" - r_frame = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(1.0, self.dt_)) + r_frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(1.0, self.dt_)) r_frame.set_frequency(4, 2.0) r_frame.set_frequency(8, 3.0) @@ -161,8 +161,8 @@ def test_phase_advance(self): """ d0 = pulse.DriveChannel(0) - sig0 = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q0")) - sig1 = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q1")) + sig0 = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q", 0)) + sig1 = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q", 1)) with pulse.build() as sched: pulse.play(sig0, d0) @@ -171,20 +171,20 @@ def test_phase_advance(self): pulse.play(sig1, d0) frames_config = FramesConfiguration.from_dict({ - pulse.Frame("Q0"): { + pulse.Frame("Q", 0): { "frequency": self.freq0, "purpose": "Frame of qubit 0.", "sample_duration": self.dt_, }, - pulse.Frame("Q1"): { + pulse.Frame("Q", 1): { "frequency": self.freq1, "purpose": "Frame of qubit 1.", "sample_duration": self.dt_, }, }) - frame0_ = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(self.freq0, self.dt_)) - frame1_ = ResolvedFrame(pulse.Frame("Q1"), FrameDefinition(self.freq1, self.dt_)) + frame0_ = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq0, self.dt_)) + frame1_ = ResolvedFrame(pulse.Frame("Q", 1), FrameDefinition(self.freq1, self.dt_)) # Check that the resolved frames are tracking phases properly for time in [0, 160, 320, 480]: @@ -227,13 +227,13 @@ def test_phase_advance(self): def test_phase_advance_with_instructions(self): """Test that the phase advances are properly computed with frame instructions.""" - sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q0")) + sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q", 0)) with pulse.build() as sched: pulse.play(sig, pulse.DriveChannel(0)) - pulse.shift_phase(1.0, pulse.Frame("Q0")) + pulse.shift_phase(1.0, pulse.Frame("Q", 0)) - frame = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(self.freq0, self.dt_)) + frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq0, self.dt_)) frame.set_frame_instructions(block_to_schedule(sched)) self.assertAlmostEqual(frame.phase(0), 0.0, places=8) @@ -252,7 +252,7 @@ def test_phase_advance_with_instructions(self): def test_set_frequency(self): """Test setting the frequency of the resolved frame.""" - frame = ResolvedFrame(pulse.Frame("Q0"), FrameDefinition(self.freq1, self.dt_)) + frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq1, self.dt_)) frame.set_frequency(16, self.freq0) frame.set_frequency(10, self.freq1) frame.set_frequency(10, self.freq1) @@ -265,18 +265,18 @@ def test_set_frequency(self): def test_broadcasting(self): """Test that resolved frames broadcast to control channels.""" - sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q0")) + sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q", 0)) with pulse.build() as sched: with pulse.align_left(): pulse.play(sig, pulse.DriveChannel(3)) - pulse.shift_phase(1.23, pulse.Frame("Q0")) + pulse.shift_phase(1.23, pulse.Frame("Q", 0)) with pulse.align_left(ignore_frames=True): pulse.play(sig, pulse.DriveChannel(3)) pulse.play(sig, pulse.ControlChannel(0)) frames_config = FramesConfiguration.from_dict({ - pulse.Frame("Q0"): { + pulse.Frame("Q", 0): { "frequency": self.freq0, "purpose": "Frame of qubit 0.", "sample_duration": self.dt_, diff --git a/test/python/pulse/test_instructions.py b/test/python/pulse/test_instructions.py index 8a14680a9c3c..57472cf16fa5 100644 --- a/test/python/pulse/test_instructions.py +++ b/test/python/pulse/test_instructions.py @@ -150,9 +150,9 @@ def test_freq(self): def test_frame(self): """Test the basic shift phase on a Frame.""" - set_freq = instructions.SetFrequency(4.5e9, Frame("Q123")) + set_freq = instructions.SetFrequency(4.5e9, Frame("Q", 123)) - self.assertEqual(set_freq.channel, Frame("Q123")) + self.assertEqual(set_freq.channel, Frame("Q", 123)) class TestShiftPhase(QiskitTestCase): @@ -177,9 +177,9 @@ def test_default(self): def test_frame(self): """Test the basic shift phase on a Frame.""" - shift_phase = instructions.ShiftPhase(1.57, Frame("Q123")) + shift_phase = instructions.ShiftPhase(1.57, Frame("Q", 123)) - self.assertEqual(shift_phase.channel, Frame("Q123")) + self.assertEqual(shift_phase.channel, Frame("Q", 123)) class TestSnapshot(QiskitTestCase): diff --git a/test/python/pulse/test_pulse_lib.py b/test/python/pulse/test_pulse_lib.py index 38bf3e752a8c..5f6778e79bdf 100644 --- a/test/python/pulse/test_pulse_lib.py +++ b/test/python/pulse/test_pulse_lib.py @@ -117,9 +117,9 @@ class TestSignal(QiskitTestCase): def test_construction(self): """Test the basics of the signal class.""" - signal = Signal(Gaussian(160, 0.1, 40), Frame("Q1")) + signal = Signal(Gaussian(160, 0.1, 40), Frame("Q", 1)) self.assertEqual(signal.pulse, Gaussian(160, 0.1, 40)) - self.assertEqual(signal.frame, Frame("Q1")) + self.assertEqual(signal.frame, Frame("Q", 1)) class TestParametricPulses(QiskitTestCase): From 582fdaf72be0bb98644580ad4371504fc011b75c Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 22 Jun 2021 16:24:52 +0200 Subject: [PATCH 085/111] * Moved frame resolution to base_transforms.py. --- qiskit/assembler/assemble_schedules.py | 8 +++----- qiskit/pulse/transforms/base_transforms.py | 9 ++++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index 6d4310a9182e..e0bdd5f918f6 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -100,14 +100,12 @@ def _assemble_experiments( ) instruction_converter = instruction_converter(qobj.PulseQobjInstruction, **run_config.to_dict()) - formatted_schedules = [transforms.target_qobj_transform(sched) for sched in schedules] - frames_config = getattr(run_config, "frames_config", None) - resolved_schedules = [ - transforms.resolve_frames(sched, frames_config) for sched in formatted_schedules + formatted_schedules = [ + transforms.target_qobj_transform(sched, frames_config) for sched in schedules ] - compressed_schedules = transforms.compress_pulses(resolved_schedules) + compressed_schedules = transforms.compress_pulses(formatted_schedules) user_pulselib = {} experiments = [] diff --git a/qiskit/pulse/transforms/base_transforms.py b/qiskit/pulse/transforms/base_transforms.py index 011c050f578a..02b1f43476b8 100644 --- a/qiskit/pulse/transforms/base_transforms.py +++ b/qiskit/pulse/transforms/base_transforms.py @@ -13,11 +13,13 @@ # TODO: replace this with proper pulse transformation passes. Qiskit-terra/#6121 -from typing import Union, Iterable, Tuple +from typing import Union, Iterable, Optional, Tuple from qiskit.pulse.instructions import Instruction from qiskit.pulse.schedule import ScheduleBlock, Schedule from qiskit.pulse.transforms import canonicalization +from qiskit.pulse.frame import FramesConfiguration +from qiskit.pulse.transforms.frames import resolve_frames InstructionSched = Union[Tuple[int, Instruction], Instruction] @@ -25,12 +27,14 @@ def target_qobj_transform( sched: Union[ScheduleBlock, Schedule, InstructionSched, Iterable[InstructionSched]], remove_directives: bool = True, + frames_config: Optional[FramesConfiguration] = None, ) -> Schedule: """A basic pulse program transformation for OpenPulse API execution. Args: sched: Input program to transform. remove_directives: Set `True` to remove compiler directives. + frames_config: The Configuration with which to resolve any frames. Returns: Transformed program for execution. @@ -52,6 +56,9 @@ def target_qobj_transform( if remove_directives: sched = canonicalization.remove_directives(sched) + # remove any instructions on frames + sched = resolve_frames(sched, frames_config) + return sched From 9927f809b9f25712f6e5bcc37088bce4e4f87fb3 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 22 Jun 2021 16:40:33 +0200 Subject: [PATCH 086/111] * Made the tolerance in frames resolution configurable. --- qiskit/pulse/frame.py | 3 +++ qiskit/pulse/transforms/frames.py | 4 ++-- qiskit/pulse/transforms/resolved_frame.py | 6 ++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 552e329ad719..da5e0e164368 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -104,6 +104,9 @@ class FrameDefinition: # The phase of the frame at time zero. phase: float = 0.0 + # Tolerance on phase and frequency shifts. Shifts below this value are ignored. + tolerance: float = 1.0e-8 + class FramesConfiguration: """A class that specifies how frames on a backend are configured.""" diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index d53c675b8279..004fb401aa43 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -88,11 +88,11 @@ def resolve_frames( freq_diff = frame_freq - channel_trackers[chan].frequency(time) phase_diff = frame_phase - channel_trackers[chan].phase(time) - if abs(freq_diff) > 1e-8: + if abs(freq_diff) > resolved_frame.tolerance: shift_freq = instructions.ShiftFrequency(freq_diff, chan) sched.insert(time, shift_freq, inplace=True) - if abs(phase_diff) > 1e-8: + if abs(phase_diff) > resolved_frame.tolerance: sched.insert(time, instructions.ShiftPhase(phase_diff, chan), inplace=True) # If the channel's phase and frequency has not been set in the past diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 6337ed07c970..beafd5ce629d 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -150,12 +150,18 @@ def __init__(self, frame: Frame, definition: FrameDefinition): ] self._purpose = definition.purpose + self._tolerance = definition.tolerance @property def purpose(self) -> str: """Return the purpose of the frame.""" return self.purpose + @property + def tolerance(self) -> float: + """Tolerance on phase and frequency shifts. Shifts below this value are ignored.""" + return self._tolerance + def set_frame_instructions(self, schedule: Schedule): """ Add all matching frame instructions in this schedule to self. From 14efefa94c5f4d580f2a7341a0693da95d1cf4af Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 25 Jun 2021 13:34:47 +0200 Subject: [PATCH 087/111] * Added a method to add dt to FramesConfiguration. --- qiskit/pulse/frame.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index da5e0e164368..c094efd5ce38 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -163,6 +163,11 @@ def definitions(self) -> List[FrameDefinition]: """Return the definitions for each frame.""" return [frame_def for frame_def in self._frames.values()] + def add_dt(self, sample_duration: float): + """Add the same duration to all frames.""" + for definition in self._frames.values(): + definition.sample_duration = sample_duration + def items(self): """Return the items in the frames config.""" return self._frames.items() From e14fbca9f6cc95bdcf0de0598b8e5d4dde9cc3e5 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 25 Jun 2021 13:42:09 +0200 Subject: [PATCH 088/111] * Made frames property mutable. --- qiskit/providers/models/pulsedefaults.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index c67c2f41aa4e..63fc9f2ec6d7 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -214,6 +214,7 @@ def __init__( self.discriminator = discriminator self._data.update(kwargs) + self._frames_config = None def __getattr__(self, name): try: @@ -285,14 +286,17 @@ def frames(self) -> FramesConfiguration: Returns: frames: A list of dicts, each dict defines a frame, its frequency, and its purpose. """ - frames = {} - for qubit, freq in enumerate(self.qubit_freq_est): - frames[Frame("Q", qubit)] = {"frequency": freq, "purpose": f"Frame of qubit {qubit}"} + if self._frames_config is None: + frames = {} + for qubit, freq in enumerate(self.qubit_freq_est): + frames[Frame("Q", qubit)] = {"frequency": freq, "purpose": f"Frame of qubit {qubit}"} - for meas, freq in enumerate(self.meas_freq_est): - frames[Frame("M", meas)] = {"frequency": freq, "purpose": f"Frame of meas {meas}"} + for meas, freq in enumerate(self.meas_freq_est): + frames[Frame("M", meas)] = {"frequency": freq, "purpose": f"Frame of meas {meas}"} - return FramesConfiguration.from_dict(frames) + self._frames_config = FramesConfiguration.from_dict(frames) + + return self._frames_config def __str__(self): qubit_freqs = [freq / 1e9 for freq in self.qubit_freq_est] From ee161ee6241a4e3ac4be869b18038c6465a56fa7 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 25 Jun 2021 13:53:34 +0200 Subject: [PATCH 089/111] * Changed location in assemble where dt is added. --- qiskit/compiler/assembler.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index a8c46fe4bbe0..5f05a90105bd 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -460,11 +460,6 @@ def _parse_pulse_args( if backend: frames_config_ = getattr(backend.defaults(), "frames", FramesConfiguration()) - # The frames in pulse defaults do not provide a dt (needed to compute phase - # advances) so we add it here. - for frame_def in frames_config_.definitions: - frame_def.sample_duration = backend_config.dt - if frames_config is None: frames_config = frames_config_ else: @@ -473,6 +468,9 @@ def _parse_pulse_args( if frame not in frames_config: frames_config[frame] = config + if backend: + frames_config.add_dt(backend_config.dt) + dynamic_reprate_enabled = getattr(backend_config, "dynamic_reprate_enabled", False) rep_time = rep_time or getattr(backend_config, "rep_times", None) From e8497587f93b2bfa7f77439942825c5d5b4ec79e Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 25 Jun 2021 13:56:28 +0200 Subject: [PATCH 090/111] * Frame is no longer optional in signal. --- qiskit/pulse/library/signal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/library/signal.py b/qiskit/pulse/library/signal.py index 87a3595f7bba..2f43015cd926 100644 --- a/qiskit/pulse/library/signal.py +++ b/qiskit/pulse/library/signal.py @@ -26,7 +26,7 @@ class Signal: Frame, i.e. a frequency and a phase. """ - def __init__(self, pulse: Pulse, frame: Optional[Frame], name: Optional[str] = None): + def __init__(self, pulse: Pulse, frame: Frame, name: Optional[str] = None): """ Args: pulse: The envelope of the signal. From 5ec2682f88b332d5dd27e2b0e389ec6fc200292b Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 25 Jun 2021 17:58:01 +0200 Subject: [PATCH 091/111] * Changed prefixes for pulse defautl frames. * Added frame property to play. * Cosmetic refactor of transforms/frames.py * PulseChannel now has a frame associated to it. --- qiskit/providers/models/pulsedefaults.py | 11 ++++++++-- qiskit/pulse/channels.py | 10 ++++++++- qiskit/pulse/instructions/play.py | 11 +++++++++- qiskit/pulse/transforms/frames.py | 26 ++++++++++++++--------- qiskit/pulse/transforms/resolved_frame.py | 2 +- test/python/pulse/test_channels.py | 4 ++++ 6 files changed, 49 insertions(+), 15 deletions(-) diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index 63fc9f2ec6d7..586342d37644 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -16,6 +16,7 @@ from typing import Any, Dict, List from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap +from qiskit.pulse.channels import DriveChannel, MeasureChannel from qiskit.pulse.schedule import Schedule from qiskit.qobj import PulseLibraryItem, PulseQobjInstruction from qiskit.qobj.converters import QobjToInstructionConverter @@ -289,10 +290,16 @@ def frames(self) -> FramesConfiguration: if self._frames_config is None: frames = {} for qubit, freq in enumerate(self.qubit_freq_est): - frames[Frame("Q", qubit)] = {"frequency": freq, "purpose": f"Frame of qubit {qubit}"} + frames[Frame(DriveChannel.prefix, qubit)] = { + "frequency": freq, + "purpose": f"Frame of qubit {qubit}" + } for meas, freq in enumerate(self.meas_freq_est): - frames[Frame("M", meas)] = {"frequency": freq, "purpose": f"Frame of meas {meas}"} + frames[Frame(MeasureChannel.prefix, meas)] = { + "frequency": freq, + "purpose": f"Frame of meas {meas}" + } self._frames_config = FramesConfiguration.from_dict(frames) diff --git a/qiskit/pulse/channels.py b/qiskit/pulse/channels.py index 03fc6fe695c3..b60df7fd15f6 100644 --- a/qiskit/pulse/channels.py +++ b/qiskit/pulse/channels.py @@ -29,6 +29,7 @@ from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType from qiskit.pulse.exceptions import PulseError from qiskit.pulse.utils import deprecated_functionality, validate_index +from qiskit.pulse.frame import Frame class Channel(metaclass=ABCMeta): @@ -165,7 +166,14 @@ def __hash__(self): class PulseChannel(Channel, metaclass=ABCMeta): """Base class of transmit Channels. Pulses can be played on these channels.""" - pass + def __init__(self, index: int): + self._frame = Frame(self.prefix, index) + super().__init__(index) + + @property + def frame(self) -> Frame: + """Return the default frame of this channel.""" + return self._frame class DriveChannel(PulseChannel): diff --git a/qiskit/pulse/instructions/play.py b/qiskit/pulse/instructions/play.py index 974c7795df0c..0f3cae466b29 100644 --- a/qiskit/pulse/instructions/play.py +++ b/qiskit/pulse/instructions/play.py @@ -22,6 +22,7 @@ from qiskit.pulse.library.signal import Signal from qiskit.pulse.instructions.instruction import Instruction from qiskit.pulse.utils import deprecated_functionality +from qiskit.pulse.frame import Frame class Play(Instruction): @@ -73,10 +74,18 @@ def pulse(self) -> Pulse: def signal(self) -> Signal: """The signal that will be played.""" if isinstance(self.operands[0], Pulse): - return Signal(self.operands[0], None) + return Signal(self.operands[0], self.operands[1].frame) return self.operands[0] + @property + def frame(self) -> Frame: + """The frame in which the pulse is played.""" + if isinstance(self.operands[0], Signal): + return self.operands[0].frame + + return self.channel.frame + @property def channel(self) -> PulseChannel: """Return the :py:class:`~qiskit.pulse.channels.Channel` that this instruction is diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 004fb401aa43..fea9c6ba33f1 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -13,6 +13,7 @@ """Replace a schedule with frames by one with instructions on PulseChannels only.""" from typing import Union +import numpy as np from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.pulse.transforms.resolved_frame import ResolvedFrame, ChannelTracker @@ -20,6 +21,7 @@ from qiskit.pulse.exceptions import PulseError from qiskit.pulse import channels as chans, instructions from qiskit.pulse.frame import FramesConfiguration +from qiskit.pulse.instructions import ShiftPhase, ShiftFrequency, Play, SetFrequency, SetPhase def resolve_frames( @@ -68,7 +70,7 @@ def resolve_frames( sched = Schedule(name=schedule.name, metadata=schedule.metadata) for time, inst in schedule.instructions: - if isinstance(inst, instructions.Play): + if isinstance(inst, Play): chan = inst.channel if inst.signal.frame is not None: @@ -89,35 +91,38 @@ def resolve_frames( phase_diff = frame_phase - channel_trackers[chan].phase(time) if abs(freq_diff) > resolved_frame.tolerance: - shift_freq = instructions.ShiftFrequency(freq_diff, chan) + shift_freq = ShiftFrequency(freq_diff, chan) sched.insert(time, shift_freq, inplace=True) if abs(phase_diff) > resolved_frame.tolerance: - sched.insert(time, instructions.ShiftPhase(phase_diff, chan), inplace=True) + sched.insert(time, ShiftPhase(phase_diff, chan), inplace=True) # If the channel's phase and frequency has not been set in the past # we set it now else: - sched.insert(time, instructions.SetFrequency(frame_freq, chan), inplace=True) - sched.insert(time, instructions.SetPhase(frame_phase, chan), inplace=True) + if frame_freq != 0: + sched.insert(time, SetFrequency(frame_freq, chan), inplace=True) + + if frame_phase != 0: + sched.insert(time, SetPhase(frame_phase, chan), inplace=True) # Update the frequency and phase of this channel. channel_trackers[chan].set_frequency(time, frame_freq) channel_trackers[chan].set_phase(time, frame_phase) - play = instructions.Play(inst.operands[0].pulse, chan) + play = Play(inst.pulse, chan) sched.insert(time, play, inplace=True) else: - sched.insert(time, instructions.Play(inst.pulse, chan), inplace=True) + sched.insert(time, Play(inst.pulse, chan), inplace=True) # Insert phase and frequency commands that are not applied to frames. - elif isinstance(inst, (instructions.SetFrequency, instructions.ShiftFrequency)): + elif isinstance(inst, (SetFrequency, ShiftFrequency)): chan = inst.channel if issubclass(type(chan), chans.PulseChannel): sched.insert(time, type(inst)(inst.frequency, chan), inplace=True) - elif isinstance(inst, (instructions.SetPhase, instructions.ShiftPhase)): + elif isinstance(inst, (SetPhase, ShiftPhase)): chan = inst.channel if issubclass(type(chan), chans.PulseChannel): @@ -133,7 +138,8 @@ def resolve_frames( ), ): sched.insert(time, inst, inplace=True) - + elif isinstance(inst, instructions.Call): + raise PulseError("Inline Call instructions before resolving frames.") else: raise PulseError(f"Unsupported {inst.__class__.__name__} in frame resolution.") diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index beafd5ce629d..b617f19fd729 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -100,7 +100,7 @@ def phase(self, time: int) -> float: freq = self.frequency(time) - return (phase + 2 * np.pi * freq * (time - last_time) * self._sample_duration) % (2 * np.pi) + return phase + 2 * np.pi * freq * (time - last_time) * self._sample_duration def set_frequency(self, time: int, frequency: float): """Insert a new frequency in the time-ordered frequencies. diff --git a/test/python/pulse/test_channels.py b/test/python/pulse/test_channels.py index 3637a1cea121..733504093629 100644 --- a/test/python/pulse/test_channels.py +++ b/test/python/pulse/test_channels.py @@ -14,6 +14,7 @@ import unittest +from qiskit.pulse.frame import Frame from qiskit.pulse.channels import ( AcquireChannel, Channel, @@ -109,6 +110,7 @@ def test_default(self): self.assertEqual(drive_channel.index, 123) self.assertEqual(drive_channel.name, "d123") + self.assertEqual(drive_channel.frame, Frame("d", 123)) class TestControlChannel(QiskitTestCase): @@ -120,6 +122,7 @@ def test_default(self): self.assertEqual(control_channel.index, 123) self.assertEqual(control_channel.name, "u123") + self.assertEqual(control_channel.frame, Frame("u", 123)) class TestMeasureChannel(QiskitTestCase): @@ -131,6 +134,7 @@ def test_default(self): self.assertEqual(measure_channel.index, 123) self.assertEqual(measure_channel.name, "m123") + self.assertEqual(measure_channel.frame, Frame("m", 123)) if __name__ == "__main__": From 990eec55ffd27202c423a9e88723ae6d04054dc6 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 25 Jun 2021 19:02:39 +0200 Subject: [PATCH 092/111] * Refactored sample_duration. --- qiskit/compiler/assembler.py | 2 +- qiskit/pulse/frame.py | 23 ++++---- qiskit/pulse/transforms/frames.py | 68 ++++++++++------------- qiskit/pulse/transforms/resolved_frame.py | 5 +- test/python/pulse/test_frames.py | 32 +++++------ 5 files changed, 58 insertions(+), 72 deletions(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index 5f05a90105bd..87ba605a70cd 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -469,7 +469,7 @@ def _parse_pulse_args( frames_config[frame] = config if backend: - frames_config.add_dt(backend_config.dt) + frames_config.sample_duration = backend_config.dt dynamic_reprate_enabled = getattr(backend_config, "dynamic_reprate_enabled", False) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index c094efd5ce38..1efadf1872cc 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -95,9 +95,6 @@ class FrameDefinition: # The frequency of the frame at time zero. frequency: float - # The duration of the samples in the control electronics. - sample_duration: float - # A user-friendly string defining the purpose of the Frame. purpose: str = None @@ -114,6 +111,7 @@ class FramesConfiguration: def __init__(self): """Initialize the frames configuration.""" self._frames = dict() + self._sample_duration = None @classmethod def from_dict(cls, frames_config: Dict[Frame, Dict]) -> "FramesConfiguration": @@ -143,7 +141,6 @@ def add_frame( self, frame: Frame, frequency: float, - sample_duration: Optional[float] = None, purpose: Optional[str] = None ): """Add a frame to the frame configuration. @@ -151,22 +148,24 @@ def add_frame( Args: frame: The frame instance to add. frequency: The frequency of the frame. - sample_duration: The sample duration. purpose: A string describing the purpose of the frame. """ - self._frames[frame] = FrameDefinition( - frequency=frequency, sample_duration=sample_duration, purpose=purpose - ) + self._frames[frame] = FrameDefinition(frequency=frequency, purpose=purpose) @property def definitions(self) -> List[FrameDefinition]: """Return the definitions for each frame.""" return [frame_def for frame_def in self._frames.values()] - def add_dt(self, sample_duration: float): - """Add the same duration to all frames.""" - for definition in self._frames.values(): - definition.sample_duration = sample_duration + @property + def sample_duration(self) -> float: + """Return the duration of a sample.""" + return self._sample_duration + + @sample_duration.setter + def sample_duration(self, sample_duration): + """Set the duration of the samples.""" + self._sample_duration = sample_duration def items(self): """Return the items in the frames config.""" diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index fea9c6ba33f1..c681a0b70078 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -50,22 +50,17 @@ def resolve_frames( schedule = block_to_schedule(schedule) resolved_frames = {} - sample_duration = None for frame, frame_def in frames_config.items(): - resolved_frames[frame] = ResolvedFrame(frame, frame_def) + resolved_frames[frame] = ResolvedFrame(frame, frame_def, frames_config.sample_duration) # Extract shift and set frame operations from the schedule. resolved_frames[frame].set_frame_instructions(schedule) - sample_duration = frame_def.sample_duration - - if sample_duration is None: - raise PulseError("Frame configuration does not have a sample duration.") # Used to keep track of the frequency and phase of the channels channel_trackers = {} for ch in schedule.channels: if isinstance(ch, chans.PulseChannel): - channel_trackers[ch] = ChannelTracker(ch, sample_duration) + channel_trackers[ch] = ChannelTracker(ch, frames_config.sample_duration) sched = Schedule(name=schedule.name, metadata=schedule.metadata) @@ -73,47 +68,42 @@ def resolve_frames( if isinstance(inst, Play): chan = inst.channel - if inst.signal.frame is not None: - frame = inst.signal.frame - - if frame not in resolved_frames: - raise PulseError(f"{frame} is not configured and cannot " f"be resolved.") + if inst.frame not in resolved_frames: + raise PulseError(f"{inst.frame} is not configured and cannot " f"be resolved.") - resolved_frame = resolved_frames[frame] + resolved_frame = resolved_frames[inst.frame] - frame_freq = resolved_frame.frequency(time) - frame_phase = resolved_frame.phase(time) + frame_freq = resolved_frame.frequency(time) + frame_phase = resolved_frame.phase(time) - # If the frequency and phase of the channel has already been set once in - # The past we compute shifts. - if channel_trackers[chan].is_initialized(): - freq_diff = frame_freq - channel_trackers[chan].frequency(time) - phase_diff = frame_phase - channel_trackers[chan].phase(time) + # If the frequency and phase of the channel has already been set once in + # the past we compute shifts. + if channel_trackers[chan].is_initialized(): + freq_diff = frame_freq - channel_trackers[chan].frequency(time) + phase_diff = frame_phase - channel_trackers[chan].phase(time) - if abs(freq_diff) > resolved_frame.tolerance: - shift_freq = ShiftFrequency(freq_diff, chan) - sched.insert(time, shift_freq, inplace=True) + if abs(freq_diff) > resolved_frame.tolerance: + shift_freq = ShiftFrequency(freq_diff, chan) + sched.insert(time, shift_freq, inplace=True) - if abs(phase_diff) > resolved_frame.tolerance: - sched.insert(time, ShiftPhase(phase_diff, chan), inplace=True) + if abs(phase_diff) > resolved_frame.tolerance: + sched.insert(time, ShiftPhase(phase_diff, chan), inplace=True) - # If the channel's phase and frequency has not been set in the past - # we set it now - else: - if frame_freq != 0: - sched.insert(time, SetFrequency(frame_freq, chan), inplace=True) + # If the channel's phase and frequency has not been set in the past + # we set it now + else: + if frame_freq != 0: + sched.insert(time, SetFrequency(frame_freq, chan), inplace=True) - if frame_phase != 0: - sched.insert(time, SetPhase(frame_phase, chan), inplace=True) + if frame_phase != 0: + sched.insert(time, SetPhase(frame_phase, chan), inplace=True) - # Update the frequency and phase of this channel. - channel_trackers[chan].set_frequency(time, frame_freq) - channel_trackers[chan].set_phase(time, frame_phase) + # Update the frequency and phase of this channel. + channel_trackers[chan].set_frequency(time, frame_freq) + channel_trackers[chan].set_phase(time, frame_phase) - play = Play(inst.pulse, chan) - sched.insert(time, play, inplace=True) - else: - sched.insert(time, Play(inst.pulse, chan), inplace=True) + play = Play(inst.pulse, chan) + sched.insert(time, play, inplace=True) # Insert phase and frequency commands that are not applied to frames. elif isinstance(inst, (SetFrequency, ShiftFrequency)): diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index b617f19fd729..1928f30a390a 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -129,13 +129,14 @@ class ResolvedFrame(Tracker): Frame at any given point in time. """ - def __init__(self, frame: Frame, definition: FrameDefinition): + def __init__(self, frame: Frame, definition: FrameDefinition, sample_duration: float): """Initialized a resolved frame. Args: frame: The frame to track. definition: An instance of the FrameDefinition dataclass which defines the frequency, phase, sample_duration, and purpose of a frame. + sample_duration: The duration of a sample. Raises: PulseError: If there are still parameters in the given frame. @@ -143,7 +144,7 @@ def __init__(self, frame: Frame, definition: FrameDefinition): if isinstance(frame.identifier[1], ParameterExpression): raise PulseError("A parameterized frame cannot initialize a ResolvedFrame.") - super().__init__(frame.name, definition.sample_duration) + super().__init__(frame.name, sample_duration) self._frequencies_phases = [ TimeFrequencyPhase(time=0, frequency=definition.frequency, phase=definition.phase) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 7aef2f02de49..ffc5f2f91513 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -58,12 +58,10 @@ def test_frame_config(self): config = { Frame("Q", 0): { "frequency": 5.5e9, - "sample_duration": 0.222e-9, "purpose": "Frame of qubit 0" }, Frame("Q", 1): { "frequency": 5.2e9, - "sample_duration": 0.222e-9, }, Frame("Q", 2): { "frequency": 5.2e9, @@ -71,11 +69,11 @@ def test_frame_config(self): } frames_config = FramesConfiguration.from_dict(config) + frames_config.sample_duration = 0.222e-9 self.assertEqual(frames_config[Frame("Q", 0)].frequency, 5.5e9) - self.assertEqual(frames_config[Frame("Q", 0)].sample_duration, 0.222e-9) + self.assertEqual(frames_config.sample_duration, 0.222e-9) self.assertEqual(frames_config[Frame("Q", 1)].frequency, 5.2e9) - self.assertTrue(frames_config[Frame("Q", 2)].sample_duration is None) for frame_def in frames_config.definitions: frame_def.sample_duration = 0.1e-9 @@ -89,13 +87,13 @@ def test_merge_two_configs(self): """Test to see if we can merge two configs.""" config1 = FramesConfiguration.from_dict({ - Frame("Q", 0): {"frequency": 5.5e9, "sample_duration": 0.222e-9}, - Frame("Q", 1): {"frequency": 5.2e9, "sample_duration": 0.222e-9}, + Frame("Q", 0): {"frequency": 5.5e9}, + Frame("Q", 1): {"frequency": 5.2e9}, }) config2 = FramesConfiguration.from_dict({ - Frame("Q", 1): {"frequency": 4.5e9, "sample_duration": 0.222e-9}, - Frame("Q", 2): {"frequency": 4.2e9, "sample_duration": 0.222e-9}, + Frame("Q", 1): {"frequency": 4.5e9}, + Frame("Q", 2): {"frequency": 4.2e9}, }) for frame, frame_def in config2.items(): @@ -118,7 +116,7 @@ def test_phase(self): """Test the phase of the resolved frames.""" two_pi_dt = 2.0j * np.pi * self.dt_ - r_frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(5.5e9, self.dt_)) + r_frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(5.5e9), self.dt_) r_frame.set_frequency(99, 5.2e9) for time in [0, 55, 98]: @@ -133,7 +131,7 @@ def test_phase(self): def test_get_phase(self): """Test that we get the correct phase as function of time.""" - r_frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(0.0, self.dt_)) + r_frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(0.0), self.dt_) r_frame.set_phase(4, 1.0) r_frame.set_phase(8, 2.0) @@ -144,7 +142,7 @@ def test_get_phase(self): def test_get_frequency(self): """Test that we get the correct phase as function of time.""" - r_frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(1.0, self.dt_)) + r_frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(1.0), self.dt_) r_frame.set_frequency(4, 2.0) r_frame.set_frequency(8, 3.0) @@ -174,17 +172,15 @@ def test_phase_advance(self): pulse.Frame("Q", 0): { "frequency": self.freq0, "purpose": "Frame of qubit 0.", - "sample_duration": self.dt_, }, pulse.Frame("Q", 1): { "frequency": self.freq1, "purpose": "Frame of qubit 1.", - "sample_duration": self.dt_, }, }) - frame0_ = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq0, self.dt_)) - frame1_ = ResolvedFrame(pulse.Frame("Q", 1), FrameDefinition(self.freq1, self.dt_)) + frame0_ = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq0), self.dt_) + frame1_ = ResolvedFrame(pulse.Frame("Q", 1), FrameDefinition(self.freq1), self.dt_) # Check that the resolved frames are tracking phases properly for time in [0, 160, 320, 480]: @@ -233,7 +229,7 @@ def test_phase_advance_with_instructions(self): pulse.play(sig, pulse.DriveChannel(0)) pulse.shift_phase(1.0, pulse.Frame("Q", 0)) - frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq0, self.dt_)) + frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq0), self.dt_) frame.set_frame_instructions(block_to_schedule(sched)) self.assertAlmostEqual(frame.phase(0), 0.0, places=8) @@ -252,7 +248,7 @@ def test_phase_advance_with_instructions(self): def test_set_frequency(self): """Test setting the frequency of the resolved frame.""" - frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq1, self.dt_)) + frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq1), self.dt_) frame.set_frequency(16, self.freq0) frame.set_frequency(10, self.freq1) frame.set_frequency(10, self.freq1) @@ -279,9 +275,9 @@ def test_broadcasting(self): pulse.Frame("Q", 0): { "frequency": self.freq0, "purpose": "Frame of qubit 0.", - "sample_duration": self.dt_, } }) + frames_config.sample_duration = self.dt_ resolved = resolve_frames(sched, frames_config).instructions From 7fe2b3a8ca25fc568a68116432d609b7fbd64873 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sat, 26 Jun 2021 10:11:39 +0200 Subject: [PATCH 093/111] * Added method to simultaneously set frequency and phase in the Tracker. --- qiskit/pulse/transforms/frames.py | 3 +-- qiskit/pulse/transforms/resolved_frame.py | 11 +++++++++ test/python/pulse/test_frames.py | 28 +++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index c681a0b70078..27cbc4e011f9 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -99,8 +99,7 @@ def resolve_frames( sched.insert(time, SetPhase(frame_phase, chan), inplace=True) # Update the frequency and phase of this channel. - channel_trackers[chan].set_frequency(time, frame_freq) - channel_trackers[chan].set_phase(time, frame_phase) + channel_trackers[chan].set_frequency_phase(time, frame_freq, frame_phase) play = Play(inst.pulse, chan) sched.insert(time, play, inplace=True) diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 1928f30a390a..c9a7348f9a5d 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -122,6 +122,17 @@ def set_phase(self, time: int, phase: float): tfp = TimeFrequencyPhase(time=time, frequency=self.frequency(time), phase=phase) self._frequencies_phases = sorted(self._frequencies_phases + [tfp], key=lambda x: x.time) + def set_frequency_phase(self, time: int, frequency: float, phase: float): + """Insert a new frequency and phase at the given time. + + Args: + time: The time in samples (i.e. measured in units of dt). + frequency: The frequency to which self is set after the given time. + phase: The phase to which self is set after the given time. + """ + tfp = TimeFrequencyPhase(time=time, frequency=frequency, phase=phase) + self._frequencies_phases = sorted(self._frequencies_phases + [tfp], key=lambda x: x.time) + class ResolvedFrame(Tracker): """ diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index ffc5f2f91513..b04997270001 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -150,6 +150,34 @@ def test_get_frequency(self): for time, phase in enumerate(expected): self.assertEqual(r_frame.frequency(time), phase) + def test_set_phase_frequency(self): + """Test the set frequency and phase methods of the channel trackers.""" + + sample_duration = 0.25 + + r_frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(1.0), sample_duration) + r_frame.set_frequency_phase(2, 2.0, 0.5) + r_frame.set_frequency_phase(4, 3.0, 0.5) + r_frame.set_frequency_phase(6, 3.0, 1.0) + + # Frequency and phases at the time they are set. + expected = [(0, 1, 0), (2, 2, .5), (4, 3, .5), (6, 3, 1)] + for time, freq, phase in expected: + self.assertEqual(r_frame.frequency(time), freq) + self.assertEqual(r_frame.phase(time), phase) + + # Frequency and phases in between setting times i.e. with phase advance. + # As we have one sample in between times when phases are set, the total + # phase is the phase at the setting time pulse the phase advance during + # one sample. + expected = [(1, 1, 0), (3, 2, .5), (5, 3, .5), (7, 3, 1)] + time_step = 1 + + for time, freq, phase in expected: + self.assertEqual(r_frame.frequency(time), freq) + + total_phase = phase + 2 * np.pi * time_step * sample_duration * freq + self.assertEqual(r_frame.phase(time), total_phase) def test_phase_advance(self): """Test that phases are properly set when frames are resolved. From ba565636db0ecec38c6fc889dcd65223ed94dd80 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sat, 26 Jun 2021 12:55:42 +0200 Subject: [PATCH 094/111] * Fix tests. --- test/python/pulse/test_frames.py | 104 +++++++++++++++++++------------ 1 file changed, 65 insertions(+), 39 deletions(-) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index b04997270001..54b037a115af 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -16,6 +16,7 @@ from qiskit.circuit import Parameter import qiskit.pulse as pulse +from qiskit.pulse.transforms import target_qobj_transform from qiskit.test import QiskitTestCase from qiskit.pulse.transforms import resolve_frames, block_to_schedule from qiskit.pulse.transforms.resolved_frame import ResolvedFrame @@ -121,12 +122,12 @@ def test_phase(self): for time in [0, 55, 98]: phase = np.angle(np.exp(two_pi_dt * 5.5e9 * time)) % (2 * np.pi) - self.assertAlmostEqual(r_frame.phase(time), phase, places=8) + self.assertAlmostEqual(r_frame.phase(time) % (2 * np.pi), phase, places=8) self.assertEqual(r_frame.frequency(time), 5.5e9) for time in [100, 112]: phase = np.angle(np.exp(two_pi_dt * (5.5e9 * 99 + 5.2e9 * (time - 99)))) % (2 * np.pi) - self.assertAlmostEqual(r_frame.phase(time), phase, places=8) + self.assertAlmostEqual(r_frame.phase(time) % (2 * np.pi), phase, places=8) def test_get_phase(self): """Test that we get the correct phase as function of time.""" @@ -206,48 +207,39 @@ def test_phase_advance(self): "purpose": "Frame of qubit 1.", }, }) + frames_config.sample_duration = self.dt_ frame0_ = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq0), self.dt_) frame1_ = ResolvedFrame(pulse.Frame("Q", 1), FrameDefinition(self.freq1), self.dt_) # Check that the resolved frames are tracking phases properly for time in [0, 160, 320, 480]: - phase = np.angle(np.exp(2.0j * np.pi * self.freq0 * time * self.dt_)) % (2 * np.pi) - self.assertAlmostEqual(frame0_.phase(time), phase, places=8) + phase = np.angle(np.exp(2.0j * np.pi * self.freq0 * time * self.dt_)) + self.assertAlmostEqual(frame0_.phase(time) % (2 * np.pi), phase % (2 * np.pi), places=8) phase = np.angle(np.exp(2.0j * np.pi * self.freq1 * time * self.dt_)) % (2 * np.pi) - self.assertAlmostEqual(frame1_.phase(time), phase, places=8) + self.assertAlmostEqual(frame1_.phase(time) % (2 * np.pi), phase % (2 * np.pi), places=8) # Check that the proper phase instructions are added to the frame resolved schedules. resolved = resolve_frames(sched, frames_config).instructions params = [ - (0, 160, self.freq0, 1), - (160, 160, self.freq1, 4), - (320, 160, self.freq0, 7), - (480, 160, self.freq1, 10), + (160, 160, self.freq1, self.freq0, 3), + (320, 160, self.freq0, self.freq1, 6), + (480, 160, self.freq1, self.freq0, 9), ] - channel_phase = 0.0 - for time, delta, frame_freq, index in params: - wanted_phase = np.angle(np.exp(2.0j * np.pi * frame_freq * time * self.dt_)) % ( - 2 * np.pi - ) + for time, delta, frame_freq, prev_freq, index in params: + desired_phase = np.angle(np.exp(2.0j * np.pi * frame_freq * time * self.dt_)) + channel_phase = np.angle(np.exp(2.0j * np.pi * prev_freq * time * self.dt_)) - phase_diff = (wanted_phase - channel_phase) % (2 * np.pi) + phase_diff = (desired_phase - channel_phase) % (2 * np.pi) - if time == 0: - self.assertTrue(isinstance(resolved[index][1], pulse.SetPhase)) - else: - self.assertTrue(isinstance(resolved[index][1], pulse.ShiftPhase)) + self.assertTrue(isinstance(resolved[index][1], pulse.ShiftPhase)) self.assertEqual(resolved[index][0], time) self.assertAlmostEqual(resolved[index][1].phase % (2 * np.pi), phase_diff, places=8) - channel_phase += np.angle(np.exp(2.0j * np.pi * frame_freq * delta * self.dt_)) % ( - 2 * np.pi - ) - def test_phase_advance_with_instructions(self): """Test that the phase advances are properly computed with frame instructions.""" @@ -263,15 +255,15 @@ def test_phase_advance_with_instructions(self): self.assertAlmostEqual(frame.phase(0), 0.0, places=8) # Test the phase right before the shift phase instruction - phase = np.angle(np.exp(2.0j * np.pi * self.freq0 * 159 * self.dt_)) % (2 * np.pi) - self.assertAlmostEqual(frame.phase(159), phase, places=8) + phase = np.angle(np.exp(2.0j * np.pi * self.freq0 * 159 * self.dt_)) + self.assertAlmostEqual(frame.phase(159) % (2*np.pi), phase % (2*np.pi), places=8) # Test the phase at and after the shift phase instruction - phase = (np.angle(np.exp(2.0j * np.pi * self.freq0 * 160 * self.dt_)) + 1.0) % (2 * np.pi) - self.assertAlmostEqual(frame.phase(160), phase, places=8) + phase = np.angle(np.exp(2.0j * np.pi * self.freq0 * 160 * self.dt_)) + 1.0 + self.assertAlmostEqual(frame.phase(160) % (2 * np.pi), phase % (2 * np.pi), places=8) - phase = (np.angle(np.exp(2.0j * np.pi * self.freq0 * 161 * self.dt_)) + 1.0) % (2 * np.pi) - self.assertAlmostEqual(frame.phase(161), phase, places=8) + phase = np.angle(np.exp(2.0j * np.pi * self.freq0 * 161 * self.dt_)) + 1.0 + self.assertAlmostEqual(frame.phase(161) % (2 * np.pi), phase % (2 * np.pi), places=8) def test_set_frequency(self): """Test setting the frequency of the resolved frame.""" @@ -320,25 +312,59 @@ def test_broadcasting(self): self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) self.assertEqual(resolved[1][0], 0) - set_phase = resolved[1][1] - self.assertTrue(isinstance(set_phase, pulse.SetPhase)) - self.assertAlmostEqual(set_phase.phase, 0.0, places=8) + play_pulse = resolved[1][1] + self.assertTrue(isinstance(play_pulse, pulse.Play)) # Next, check that we do phase shifts on the DriveChannel after the first Gaussian. - self.assertEqual(resolved[3][0], 160) - shift_phase = resolved[3][1] + self.assertEqual(resolved[2][0], 160) + shift_phase = resolved[2][1] self.assertTrue(isinstance(shift_phase, pulse.ShiftPhase)) self.assertAlmostEqual(shift_phase.phase, 1.23, places=8) # Up to now, no pulse has been applied on the ControlChannel so we should # encounter a Set instructions at time 160 which is when the first pulse # is played on ControlChannel(0) - self.assertEqual(resolved[4][0], 160) - set_freq = resolved[4][1] + self.assertEqual(resolved[3][0], 160) + set_freq = resolved[3][1] self.assertTrue(isinstance(set_freq, pulse.SetFrequency)) self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) - self.assertEqual(resolved[5][0], 160) - set_phase = resolved[5][1] + self.assertEqual(resolved[4][0], 160) + set_phase = resolved[4][1] self.assertTrue(isinstance(set_phase, pulse.SetPhase)) - self.assertAlmostEqual(set_phase.phase, phase, places=8) + self.assertAlmostEqual(set_phase.phase % (2 * np.pi), phase, places=8) + + def test_implicit_frame(self): + """If a frame is not specified then the frame of the channel is assumed.""" + + qt = 0 + frame01 = Frame("d", qt) + frame12 = Frame("t", qt) + xp01 = pulse.Gaussian(160, 0.2, 40) + xp12 = pulse.Gaussian(160, 0.2, 40) + d_chan = pulse.DriveChannel(qt) + + # Create a schedule in which frames are specified for all pulses. + with pulse.build() as rabi_sched_explicit: + pulse.play(pulse.Signal(xp01, frame01), d_chan) + pulse.play(pulse.Signal(xp12, frame12), d_chan) + pulse.play(pulse.Signal(xp01, frame01), d_chan) + + # Create a schedule in which frames are implicitly specified for qubit transitions. + with pulse.build() as rabi_sched_implicit: + pulse.play(xp01, d_chan) + pulse.play(pulse.Signal(xp12, frame12), d_chan) + pulse.play(xp01, d_chan) + + frames_config = FramesConfiguration.from_dict({ + frame01: {"frequency": 5.5e9, "purpose": "Frame of 0 <-> 1."}, + frame12: {"frequency": 5.2e9, "purpose": "Frame of 1 <-> 2."}, + }) + frames_config.sample_duration = 0.222e-9 + + transform = lambda sched: target_qobj_transform(sched, frames_config=frames_config) + + resolved_explicit = transform(rabi_sched_explicit) + resolved_implicit = transform(rabi_sched_implicit) + + self.assertEqual(resolved_explicit, resolved_implicit) From cf319f2acf105d67810ebb8bb982aa8ec41b1e50 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sat, 26 Jun 2021 13:33:01 +0200 Subject: [PATCH 095/111] * Improved documentation for the frames config. --- qiskit/pulse/frame.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 1efadf1872cc..661da83b7e68 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -106,7 +106,13 @@ class FrameDefinition: class FramesConfiguration: - """A class that specifies how frames on a backend are configured.""" + """A class that specifies how frames on a backend are configured. + + Internally this class stores the frames configuration in a dictionary where frames + are keys with a corresponding instance of :class:`FrameDefinition` as value. All + frames are required to have the same sample duration which is stored in a separate + property of the :class:`FramesConfiguration`. + """ def __init__(self): """Initialize the frames configuration.""" @@ -146,9 +152,12 @@ def add_frame( """Add a frame to the frame configuration. Args: - frame: The frame instance to add. - frequency: The frequency of the frame. - purpose: A string describing the purpose of the frame. + frame: The frame instance to add. This instance will be a key in the internal + dictionary that allows the frame resolution mechanism to identify the + frequency of the frame. + frequency: The frequency of the frame which will determine the frequency of + all pulses played in the frame added to the configuration. + purpose: A human readable string describing the purpose of the frame. """ self._frames[frame] = FrameDefinition(frequency=frequency, purpose=purpose) From b8aa3baf0d4452bdc29ccd578290a39d8f398e3b Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sun, 27 Jun 2021 11:21:06 +0200 Subject: [PATCH 096/111] * Pop the frames config from the qobj_config. --- qiskit/assembler/assemble_schedules.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index e0bdd5f918f6..f3880a1353ec 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -332,13 +332,6 @@ def _assemble_config( if m_los: qobj_config["meas_lo_freq"] = [freq / 1e9 for freq in m_los] - # frames config: replace the frame instance by its name. - frames_config = qobj_config.get("frames_config", None) - if frames_config: - frames_config_ = {} - for frame, settings in frames_config.items(): - frames_config_[frame.name] = settings - - qobj_config["frames_config"] = frames_config_ + qobj_config.pop("frames_config", None) return qobj.PulseQobjConfig(**qobj_config) From 282e4a5a09487a84d13e15e0284d21ef14de22f7 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sun, 27 Jun 2021 13:55:09 +0200 Subject: [PATCH 097/111] * Added condition to see if frames need resolving. --- qiskit/pulse/transforms/frames.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 27cbc4e011f9..b7230bfacb01 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -13,12 +13,12 @@ """Replace a schedule with frames by one with instructions on PulseChannels only.""" from typing import Union -import numpy as np from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.pulse.transforms.resolved_frame import ResolvedFrame, ChannelTracker from qiskit.pulse.transforms.canonicalization import block_to_schedule from qiskit.pulse.exceptions import PulseError +from qiskit.pulse.library.signal import Signal from qiskit.pulse import channels as chans, instructions from qiskit.pulse.frame import FramesConfiguration from qiskit.pulse.instructions import ShiftPhase, ShiftFrequency, Play, SetFrequency, SetPhase @@ -49,6 +49,15 @@ def resolve_frames( if isinstance(schedule, ScheduleBlock): schedule = block_to_schedule(schedule) + # Check that the schedule has any frame instructions that need resolving. + # TODO Need to check phase/frequency instructions. + for time, inst in schedule.instructions: + if isinstance(inst, Play): + if isinstance(inst.operands[0], Signal): + break + else: + return schedule + resolved_frames = {} for frame, frame_def in frames_config.items(): resolved_frames[frame] = ResolvedFrame(frame, frame_def, frames_config.sample_duration) From 96d205c630379d4c075c290a865e553475a12753 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sun, 27 Jun 2021 14:12:53 +0200 Subject: [PATCH 098/111] * Added frames config to the common args. --- qiskit/compiler/assembler.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index 87ba605a70cd..a3a0dfca125f 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -180,6 +180,7 @@ def assemble( qubit_lo_range, meas_lo_range, schedule_los, + frames_config, **run_config, ) @@ -216,7 +217,6 @@ def assemble( memory_slot_size, rep_time, parametric_pulses, - frames_config, **run_config_common_dict, ) @@ -249,6 +249,7 @@ def _parse_common_args( qubit_lo_range, meas_lo_range, schedule_los, + frames_config, **run_config, ): """Resolve the various types of args allowed to the assemble() function through @@ -366,6 +367,21 @@ def _parse_common_args( "'use_measure_esp' is unset or set to 'False'." ) + frames_config_ = FramesConfiguration() + if backend: + frames_config_ = getattr(backend.defaults(), "frames", FramesConfiguration()) + + if frames_config is None: + frames_config = frames_config_ + else: + for frame, config in frames_config_.items(): + # Do not override the frames provided by the user. + if frame not in frames_config: + frames_config[frame] = config + + if backend: + frames_config.sample_duration = backend_config.dt + # create run configuration and populate run_config_dict = dict( shots=shots, @@ -381,6 +397,7 @@ def _parse_common_args( meas_lo_range=meas_lo_range, schedule_los=schedule_los, n_qubits=n_qubits, + frames_config=frames_config, **run_config, ) @@ -456,21 +473,6 @@ def _parse_pulse_args( meas_map = meas_map or getattr(backend_config, "meas_map", None) - frames_config_ = FramesConfiguration() - if backend: - frames_config_ = getattr(backend.defaults(), "frames", FramesConfiguration()) - - if frames_config is None: - frames_config = frames_config_ - else: - for frame, config in frames_config_.items(): - # Do not override the frames provided by the user. - if frame not in frames_config: - frames_config[frame] = config - - if backend: - frames_config.sample_duration = backend_config.dt - dynamic_reprate_enabled = getattr(backend_config, "dynamic_reprate_enabled", False) rep_time = rep_time or getattr(backend_config, "rep_times", None) From d98288cb3386479f000ad4e77d5446b341527069 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sun, 27 Jun 2021 14:14:09 +0200 Subject: [PATCH 099/111] * Added assembly tests. --- test/python/pulse/test_frames.py | 89 +++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 54b037a115af..ed3f44725be4 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -14,7 +14,8 @@ import numpy as np -from qiskit.circuit import Parameter +from qiskit import schedule, assemble +from qiskit.circuit import Parameter, QuantumCircuit, Gate import qiskit.pulse as pulse from qiskit.pulse.transforms import target_qobj_transform from qiskit.test import QiskitTestCase @@ -22,6 +23,7 @@ from qiskit.pulse.transforms.resolved_frame import ResolvedFrame from qiskit.pulse.parameter_manager import ParameterManager from qiskit.pulse.frame import Frame, FramesConfiguration, FrameDefinition +from qiskit.test.mock import FakeAthens class TestFrame(QiskitTestCase): @@ -368,3 +370,88 @@ def test_implicit_frame(self): resolved_implicit = transform(rabi_sched_implicit) self.assertEqual(resolved_explicit, resolved_implicit) + + +class TestFrameAssembly(QiskitTestCase): + """Test that the assembler resolves the frames.""" + + def setUp(self): + """Setup the tests.""" + + self.backend = FakeAthens() + self.defaults = self.backend.defaults() + self.qubit = 0 + + # Setup the frames + freq12 = self.defaults.qubit_freq_est[self.qubit] - 330e6 + frames_config = self.backend.defaults().frames + frames_config.add_frame(Frame("t", self.qubit), freq12, purpose="Frame of qutrit.") + frames_config.sample_duration = self.backend.configuration().dt + + self.xp01 = self.defaults.instruction_schedule_map.get( + 'x', qubits=self.qubit + ).instructions[0][1].pulse + + self.xp12 = pulse.Gaussian(160, 0.1, 40) + + # Create the expected schedule with all the explicit frames + with pulse.build(backend=self.backend, default_alignment="sequential") as expected_schedule: + pulse.play(pulse.Signal(self.xp01, pulse.Frame("d", self.qubit)), pulse.drive_channel(self.qubit)) + pulse.play(pulse.Signal(self.xp12, pulse.Frame("t", self.qubit)), pulse.drive_channel(self.qubit)) + pulse.play(pulse.Signal(self.xp01, pulse.Frame("d", self.qubit)), pulse.drive_channel(self.qubit)) + pulse.measure(qubits=[self.qubit]) + + self.expected_schedule = expected_schedule + + def test_frames_in_circuit(self): + """ + Test that we can add schedules with frames in the calibrations of a circuit + and that it will compile to the expected outcome. + """ + + with pulse.build(backend=self.backend, default_alignment="sequential") as xp12_schedule: + pulse.play(pulse.Signal(self.xp12, Frame("t", self.qubit)), pulse.drive_channel(self.qubit)) + + # Create a quantum circuit and attach the pulse to it. + circ = QuantumCircuit(1) + circ.x(0) + circ.append(Gate("xp12", num_qubits=1, params=[]), (0,)) + circ.x(0) + circ.measure_active() + circ.add_calibration("xp12", schedule=xp12_schedule, qubits=[0]) + + transform = lambda sched: target_qobj_transform( + sched, frames_config=self.backend.defaults().frames + ) + + sched_resolved = transform(schedule(circ, self.backend)) + + expected_resolved = transform(self.expected_schedule) + + self.assertEqual(sched_resolved, expected_resolved) + + def test_transforms_frame_resolution(self): + """Test that resolving frames multiple times does not change the schedule.""" + + transform = lambda sched: target_qobj_transform( + sched, frames_config=self.backend.defaults().frames + ) + + resolved1 = transform(self.expected_schedule) + resolved2 = transform(resolved1) + + self.assertEqual(resolved1, resolved2) + + def test_assmble_frames(self): + """Test that the assembler resolves the frames of schedule experiments.""" + + # Assemble a schedule with frames. + qobj = assemble(self.expected_schedule, self.backend) + + # Manually resolve frames and assemble. + transform = lambda sched: target_qobj_transform( + sched, frames_config=self.backend.defaults().frames + ) + qobj_resolved = assemble(transform(self.expected_schedule), self.backend) + + self.assertEqual(qobj.experiments, qobj_resolved.experiments) From c9eee294f9cab0250528860282527012b5a986a8 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sun, 27 Jun 2021 14:14:27 +0200 Subject: [PATCH 100/111] * Added frames config bug fix in transforms. --- qiskit/assembler/assemble_schedules.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index f3880a1353ec..8816e93fb60f 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -102,7 +102,7 @@ def _assemble_experiments( frames_config = getattr(run_config, "frames_config", None) formatted_schedules = [ - transforms.target_qobj_transform(sched, frames_config) for sched in schedules + transforms.target_qobj_transform(sched, frames_config=frames_config) for sched in schedules ] compressed_schedules = transforms.compress_pulses(formatted_schedules) @@ -183,7 +183,9 @@ def _assemble_instructions( A list of converted instructions, the user pulse library dictionary (from pulse name to pulse samples), and the maximum number of readout memory slots used by this Schedule. """ - sched = transforms.target_qobj_transform(sched) + sched = transforms.target_qobj_transform( + sched, frames_config=getattr(run_config, "frames_config", None) + ) max_memory_slot = 0 qobj_instructions = [] From f10a8ab77b5763902aea5cb07f9ea61379d12f70 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sun, 27 Jun 2021 14:53:45 +0200 Subject: [PATCH 101/111] * Added condition to catch frame instructions in resolve. --- qiskit/pulse/transforms/frames.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index b7230bfacb01..d53150673391 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -19,6 +19,7 @@ from qiskit.pulse.transforms.canonicalization import block_to_schedule from qiskit.pulse.exceptions import PulseError from qiskit.pulse.library.signal import Signal +from qiskit.pulse.frame import Frame from qiskit.pulse import channels as chans, instructions from qiskit.pulse.frame import FramesConfiguration from qiskit.pulse.instructions import ShiftPhase, ShiftFrequency, Play, SetFrequency, SetPhase @@ -50,11 +51,14 @@ def resolve_frames( schedule = block_to_schedule(schedule) # Check that the schedule has any frame instructions that need resolving. - # TODO Need to check phase/frequency instructions. for time, inst in schedule.instructions: if isinstance(inst, Play): if isinstance(inst.operands[0], Signal): break + + if isinstance(inst, (SetFrequency, SetPhase, ShiftFrequency, ShiftPhase)): + if isinstance(inst.channel, Frame): + break else: return schedule From df7ac4ef6e07f9def4bb666dce553d70ecf832bb Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sun, 27 Jun 2021 21:06:41 +0200 Subject: [PATCH 102/111] * Shift and Set instructions now only use frames. Channels are still supported for backward compatibility. --- qiskit/providers/models/pulsedefaults.py | 6 ++- qiskit/pulse/frame.py | 25 +++++++++-- qiskit/pulse/instructions/frequency.py | 50 ++++++++++++++++----- qiskit/pulse/instructions/instruction.py | 3 ++ qiskit/pulse/instructions/phase.py | 48 +++++++++++++++----- qiskit/pulse/transforms/frames.py | 31 +++++++------ qiskit/pulse/transforms/resolved_frame.py | 32 ++++++++----- qiskit/qobj/converters/pulse_instruction.py | 21 +++++---- test/python/pulse/test_parameters.py | 13 +----- 9 files changed, 155 insertions(+), 74 deletions(-) diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index 586342d37644..fed6fcb4cd8b 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -292,13 +292,15 @@ def frames(self) -> FramesConfiguration: for qubit, freq in enumerate(self.qubit_freq_est): frames[Frame(DriveChannel.prefix, qubit)] = { "frequency": freq, - "purpose": f"Frame of qubit {qubit}" + "purpose": f"Frame of qubit {qubit}", + "has_physical_channel": True, } for meas, freq in enumerate(self.meas_freq_est): frames[Frame(MeasureChannel.prefix, meas)] = { "frequency": freq, - "purpose": f"Frame of meas {meas}" + "purpose": f"Frame of meas {meas}", + "has_physical_channel": True, } self._frames_config = FramesConfiguration.from_dict(frames) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 661da83b7e68..739cbd83b7ab 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -13,9 +13,9 @@ """Implements a Frame.""" from dataclasses import dataclass -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Set, Tuple, Union -from qiskit.circuit import Parameter +from qiskit.circuit import Parameter, ParameterExpression from qiskit.pulse.utils import validate_index from qiskit.pulse.exceptions import PulseError @@ -69,6 +69,18 @@ def name(self) -> str: return f"{self._identifier[0]}{self._identifier[1]}" + @property + def parameters(self) -> Set: + """Parameters which determine the frame index.""" + if isinstance(self._identifier[1], ParameterExpression): + return self._identifier[1].parameters + + return set() + + def is_parameterized(self) -> bool: + """Return true if the identifier has a parameter.""" + return isinstance(self._identifier[1], ParameterExpression) + def __repr__(self): return f"{self.__class__.__name__}({self.name})" @@ -98,6 +110,9 @@ class FrameDefinition: # A user-friendly string defining the purpose of the Frame. purpose: str = None + # True if this frame is the native frame of a physical channel + has_physical_channel: bool = False + # The phase of the frame at time zero. phase: float = 0.0 @@ -147,6 +162,7 @@ def add_frame( self, frame: Frame, frequency: float, + has_physical_channel: Optional[bool] = False, purpose: Optional[str] = None ): """Add a frame to the frame configuration. @@ -157,9 +173,12 @@ def add_frame( frequency of the frame. frequency: The frequency of the frame which will determine the frequency of all pulses played in the frame added to the configuration. + has_physical_channel: Whether this frame is the native frame of a physical channel. purpose: A human readable string describing the purpose of the frame. """ - self._frames[frame] = FrameDefinition(frequency=frequency, purpose=purpose) + self._frames[frame] = FrameDefinition(frequency=frequency, + has_physical_channel=has_physical_channel, + purpose=purpose) @property def definitions(self) -> List[FrameDefinition]: diff --git a/qiskit/pulse/instructions/frequency.py b/qiskit/pulse/instructions/frequency.py index 954a621a9167..2b7cfae1e784 100644 --- a/qiskit/pulse/instructions/frequency.py +++ b/qiskit/pulse/instructions/frequency.py @@ -14,11 +14,13 @@ the frequency of a channel. """ from typing import Optional, Union, Tuple +import warnings from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.pulse.channels import PulseChannel from qiskit.pulse.frame import Frame from qiskit.pulse.instructions.instruction import Instruction +from qiskit.pulse.exceptions import PulseError class SetFrequency(Instruction): @@ -38,7 +40,7 @@ class SetFrequency(Instruction): def __init__( self, frequency: Union[float, ParameterExpression], - channel: Union[PulseChannel, Frame], + frame: Union[PulseChannel, Frame], name: Optional[str] = None, ): """Creates a new set channel frequency instruction. @@ -48,9 +50,17 @@ def __init__( channel: The channel or frame this instruction operates on. name: Name of this set channel frequency instruction. """ + if isinstance(frame, PulseChannel): + warnings.warn( + f"Applying {self.__class__.__name__} to channel {frame}. This " + f"functionality will be deprecated. Using frame {frame.frame}. " + f"Instead, apply {self.__class__.__name__} to a frame." + ) + frame = frame.frame + if not isinstance(frequency, ParameterExpression): frequency = float(frequency) - super().__init__(operands=(frequency, channel), name=name) + super().__init__(operands=(frequency, frame), name=name) @property def frequency(self) -> Union[float, ParameterExpression]: @@ -58,16 +68,21 @@ def frequency(self) -> Union[float, ParameterExpression]: return self.operands[0] @property - def channel(self) -> [PulseChannel, Frame]: + def channel(self) -> PulseChannel: """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ + raise PulseError(f"{self} applies to a {type(self.operands[1])}") + + @property + def frame(self) -> Frame: + """Return the frame on which this instruction applies.""" return self.operands[1] @property def channels(self) -> Tuple[PulseChannel]: """Returns the channels that this schedule uses.""" - return (self.channel,) + return tuple() @property def duration(self) -> int: @@ -76,7 +91,7 @@ def duration(self) -> int: def is_parameterized(self) -> bool: """Return True iff the instruction is parameterized.""" - return isinstance(self.frequency, ParameterExpression) or super().is_parameterized() + return isinstance(self.frequency, ParameterExpression) or self.frame.is_parameterized() class ShiftFrequency(Instruction): @@ -85,19 +100,27 @@ class ShiftFrequency(Instruction): def __init__( self, frequency: Union[float, ParameterExpression], - channel: Union[PulseChannel, Frame], + frame: Union[PulseChannel, Frame], name: Optional[str] = None, ): """Creates a new shift frequency instruction. Args: frequency: Frequency shift of the channel in Hz. - channel: The channel or frame this instruction operates on. + frame: The frame or channel this instruction operates on. name: Name of this set channel frequency instruction. """ + if isinstance(frame, PulseChannel): + warnings.warn( + f"Applying {self.__class__.__name__} to channel {frame}. This " + f"functionality will be deprecated. Using frame {frame.frame}. " + f"Instead, apply {self.__class__.__name__} to a frame." + ) + frame = frame.frame + if not isinstance(frequency, ParameterExpression): frequency = float(frequency) - super().__init__(operands=(frequency, channel), name=name) + super().__init__(operands=(frequency, frame), name=name) @property def frequency(self) -> Union[float, ParameterExpression]: @@ -105,16 +128,21 @@ def frequency(self) -> Union[float, ParameterExpression]: return self.operands[0] @property - def channel(self) -> [PulseChannel, Frame]: + def channel(self) -> PulseChannel: """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ + raise PulseError(f"{self} applies to a {type(self.operands[1])}") + + @property + def frame(self) -> Frame: + """Return the frame on which this instruction applies.""" return self.operands[1] @property def channels(self) -> Tuple[PulseChannel]: """Returns the channels that this schedule uses.""" - return (self.channel,) + return tuple() @property def duration(self) -> int: @@ -123,4 +151,4 @@ def duration(self) -> int: def is_parameterized(self) -> bool: """Return True iff the instruction is parameterized.""" - return isinstance(self.frequency, ParameterExpression) or super().is_parameterized() + return isinstance(self.frequency, ParameterExpression) or self.frame.is_parameterized() diff --git a/qiskit/pulse/instructions/instruction.py b/qiskit/pulse/instructions/instruction.py index 2363fa501e76..986321519844 100644 --- a/qiskit/pulse/instructions/instruction.py +++ b/qiskit/pulse/instructions/instruction.py @@ -259,6 +259,9 @@ def _initialize_parameter_table(self, operands: Tuple[Any]): elif isinstance(op, Channel) and isinstance(op.index, ParameterExpression): for param in op.index.parameters: self._parameter_table[param].append(idx) + elif isinstance(op, Frame) and op.is_parameterized(): + for param in op.parameters: + self._parameter_table[param].append(idx) @deprecated_functionality def assign_parameters( diff --git a/qiskit/pulse/instructions/phase.py b/qiskit/pulse/instructions/phase.py index 0900b95efe4e..b7dd2a933056 100644 --- a/qiskit/pulse/instructions/phase.py +++ b/qiskit/pulse/instructions/phase.py @@ -16,11 +16,13 @@ relative amount. """ from typing import Optional, Union, Tuple +import warnings from qiskit.circuit import ParameterExpression from qiskit.pulse.channels import PulseChannel from qiskit.pulse.frame import Frame from qiskit.pulse.instructions.instruction import Instruction +from qiskit.pulse.exceptions import PulseError class ShiftPhase(Instruction): @@ -43,7 +45,7 @@ class ShiftPhase(Instruction): def __init__( self, phase: Union[complex, ParameterExpression], - channel: Union[PulseChannel, Frame], + frame: Union[PulseChannel, Frame], name: Optional[str] = None, ): """Instantiate a shift phase instruction, increasing the output signal phase on ``channel`` @@ -51,10 +53,18 @@ def __init__( Args: phase: The rotation angle in radians. - channel: The channel or frame this instruction operates on. + frame: The channel or frame this instruction operates on. name: Display name for this instruction. """ - super().__init__(operands=(phase, channel), name=name) + if isinstance(frame, PulseChannel): + warnings.warn( + f"Applying {self.__class__.__name__} to channel {frame}. This " + f"functionality will be deprecated. Using frame {frame.frame}. " + f"Instead, apply {self.__class__.__name__} to a frame." + ) + frame = frame.frame + + super().__init__(operands=(phase, frame), name=name) @property def phase(self) -> Union[complex, ParameterExpression]: @@ -66,12 +76,17 @@ def channel(self) -> Union[PulseChannel, Frame]: """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ + raise PulseError(f"{self} applies to a {type(self.operands[1])}") + + @property + def frame(self) -> Frame: + """Return the frame on which this instruction applies.""" return self.operands[1] @property def channels(self) -> Tuple[PulseChannel]: """Returns the channels that this schedule uses.""" - return (self.channel,) + return tuple() @property def duration(self) -> int: @@ -80,7 +95,7 @@ def duration(self) -> int: def is_parameterized(self) -> bool: """Return True iff the instruction is parameterized.""" - return isinstance(self.phase, ParameterExpression) or super().is_parameterized() + return isinstance(self.phase, ParameterExpression) or self.frame.is_parameterized() class SetPhase(Instruction): @@ -99,7 +114,7 @@ class SetPhase(Instruction): def __init__( self, phase: Union[complex, ParameterExpression], - channel: Union[PulseChannel, Frame], + frame: Union[PulseChannel, Frame], name: Optional[str] = None, ): """Instantiate a set phase instruction, setting the output signal phase on ``channel`` @@ -107,10 +122,18 @@ def __init__( Args: phase: The rotation angle in radians. - channel: The channel or frame this instruction operates on. + frame: The frame or channel this instruction operates on. name: Display name for this instruction. """ - super().__init__(operands=(phase, channel), name=name) + if isinstance(frame, PulseChannel): + warnings.warn( + f"Applying {self.__class__.__name__} to channel {frame}. This " + f"functionality will be deprecated. Using frame {frame.frame}. " + f"Instead, apply {self.__class__.__name__} to a frame." + ) + frame = frame.frame + + super().__init__(operands=(phase, frame), name=name) @property def phase(self) -> Union[complex, ParameterExpression]: @@ -122,12 +145,17 @@ def channel(self) -> Union[PulseChannel, Frame]: """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ + raise PulseError(f"{self} applies to a {type(self.operands[1])}") + + @property + def frame(self) -> Frame: + """Return the frame on which this instruction applies.""" return self.operands[1] @property def channels(self) -> Tuple[PulseChannel]: """Returns the channels that this schedule uses.""" - return (self.channel,) + return tuple() @property def duration(self) -> int: @@ -136,4 +164,4 @@ def duration(self) -> int: def is_parameterized(self) -> bool: """Return True iff the instruction is parameterized.""" - return isinstance(self.phase, ParameterExpression) or super().is_parameterized() + return isinstance(self.phase, ParameterExpression) or self.frame.is_parameterized() diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index d53150673391..01ea6ff050b8 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -19,7 +19,6 @@ from qiskit.pulse.transforms.canonicalization import block_to_schedule from qiskit.pulse.exceptions import PulseError from qiskit.pulse.library.signal import Signal -from qiskit.pulse.frame import Frame from qiskit.pulse import channels as chans, instructions from qiskit.pulse.frame import FramesConfiguration from qiskit.pulse.instructions import ShiftPhase, ShiftFrequency, Play, SetFrequency, SetPhase @@ -51,13 +50,17 @@ def resolve_frames( schedule = block_to_schedule(schedule) # Check that the schedule has any frame instructions that need resolving. + # A schedule needs resolving if: + # - A pulse in a frame different from the channel's frame is played, and + # - A Set/Shift instruction is applied on a frame which does not correspond to a channel frame. for time, inst in schedule.instructions: if isinstance(inst, Play): if isinstance(inst.operands[0], Signal): break if isinstance(inst, (SetFrequency, SetPhase, ShiftFrequency, ShiftPhase)): - if isinstance(inst.channel, Frame): + frame = inst.frame + if not frames_config[frame].has_physical_channel: break else: return schedule @@ -80,6 +83,7 @@ def resolve_frames( for time, inst in schedule.instructions: if isinstance(inst, Play): chan = inst.channel + chan_frame = channel_trackers[chan].frame if inst.frame not in resolved_frames: raise PulseError(f"{inst.frame} is not configured and cannot " f"be resolved.") @@ -96,20 +100,19 @@ def resolve_frames( phase_diff = frame_phase - channel_trackers[chan].phase(time) if abs(freq_diff) > resolved_frame.tolerance: - shift_freq = ShiftFrequency(freq_diff, chan) - sched.insert(time, shift_freq, inplace=True) + sched.insert(time, ShiftFrequency(freq_diff, chan_frame), inplace=True) if abs(phase_diff) > resolved_frame.tolerance: - sched.insert(time, ShiftPhase(phase_diff, chan), inplace=True) + sched.insert(time, ShiftPhase(phase_diff, chan_frame), inplace=True) # If the channel's phase and frequency has not been set in the past # we set it now else: if frame_freq != 0: - sched.insert(time, SetFrequency(frame_freq, chan), inplace=True) + sched.insert(time, SetFrequency(frame_freq, chan_frame), inplace=True) if frame_phase != 0: - sched.insert(time, SetPhase(frame_phase, chan), inplace=True) + sched.insert(time, SetPhase(frame_phase, chan_frame), inplace=True) # Update the frequency and phase of this channel. channel_trackers[chan].set_frequency_phase(time, frame_freq, frame_phase) @@ -117,18 +120,14 @@ def resolve_frames( play = Play(inst.pulse, chan) sched.insert(time, play, inplace=True) - # Insert phase and frequency commands that are not applied to frames. + # Insert phase and frequency commands that are ties to physical channels. elif isinstance(inst, (SetFrequency, ShiftFrequency)): - chan = inst.channel - - if issubclass(type(chan), chans.PulseChannel): - sched.insert(time, type(inst)(inst.frequency, chan), inplace=True) + if frames_config[inst.frame].has_physical_channel: + sched.insert(time, inst, inplace=True) elif isinstance(inst, (SetPhase, ShiftPhase)): - chan = inst.channel - - if issubclass(type(chan), chans.PulseChannel): - sched.insert(time, type(inst)(inst.phase, chan), inplace=True) + if frames_config[inst.frame].has_physical_channel: + sched.insert(time, inst, inplace=True) elif isinstance( inst, diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index c9a7348f9a5d..a5405d9824c6 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -163,6 +163,7 @@ def __init__(self, frame: Frame, definition: FrameDefinition, sample_duration: f self._purpose = definition.purpose self._tolerance = definition.tolerance + self._has_physical_channel = definition.has_physical_channel @property def purpose(self) -> str: @@ -185,21 +186,22 @@ def set_frame_instructions(self, schedule: Schedule): PulseError: if the internal filtering does not contain the right instructions. """ - frame_instruction_types = [ShiftPhase, SetPhase, ShiftFrequency, SetFrequency] + frame_instruction_types = (ShiftPhase, SetPhase, ShiftFrequency, SetFrequency) frame_instructions = schedule.filter(instruction_types=frame_instruction_types) for time, inst in frame_instructions.instructions: - if isinstance(inst.operands[1], Frame) and self.identifier == inst.operands[1].name: - if isinstance(inst, ShiftFrequency): - self.set_frequency(time, self.frequency(time) + inst.frequency) - elif isinstance(inst, SetFrequency): - self.set_frequency(time, inst.frequency) - elif isinstance(inst, ShiftPhase): - self.set_phase(time, self.phase(time) + inst.phase) - elif isinstance(inst, SetPhase): - self.set_phase(time, inst.phase) - else: - raise PulseError("Unexpected frame operation.") + if isinstance(inst, frame_instruction_types) and self.identifier == inst.frame.name: + if not self._has_physical_channel: + if isinstance(inst, ShiftFrequency): + self.set_frequency(time, self.frequency(time) + inst.frequency) + elif isinstance(inst, SetFrequency): + self.set_frequency(time, inst.frequency) + elif isinstance(inst, ShiftPhase): + self.set_phase(time, self.phase(time) + inst.phase) + elif isinstance(inst, SetPhase): + self.set_phase(time, inst.phase) + else: + raise PulseError("Unexpected frame operation.") def __repr__(self): return f"{self.__class__.__name__}({self.identifier}, {self.frequency(0)})" @@ -216,6 +218,12 @@ def __init__(self, channel: PulseChannel, sample_duration: float): """ super().__init__(channel.name, sample_duration) self._channel = channel + self._frame = Frame(channel.prefix, channel.index) + + @property + def frame(self) -> Frame: + """Return the native frame of this channel.""" + return self._frame def is_initialized(self) -> bool: """Return true if the channel has been initialized.""" diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py index 5782d7a8ad85..1de06406f6da 100644 --- a/qiskit/qobj/converters/pulse_instruction.py +++ b/qiskit/qobj/converters/pulse_instruction.py @@ -26,6 +26,7 @@ from qiskit.pulse.exceptions import QiskitError from qiskit.pulse.parser import parse_string_expr from qiskit.pulse.schedule import Schedule +from qiskit.pulse.frame import Frame from qiskit.qobj import QobjMeasurementOption from qiskit.qobj.utils import MeasLevel from qiskit.circuit import Parameter, ParameterExpression @@ -295,7 +296,7 @@ def convert_set_frequency(self, shift, instruction): command_dict = { "name": "setf", "t0": shift + instruction.start_time, - "ch": instruction.channel.name, + "ch": instruction.frame.name, "frequency": instruction.frequency / 1e9, } return self._qobj_model(**command_dict) @@ -314,7 +315,7 @@ def convert_shift_frequency(self, shift, instruction): command_dict = { "name": "shiftf", "t0": shift + instruction.start_time, - "ch": instruction.channel.name, + "ch": instruction.frame.name, "frequency": instruction.frequency / 1e9, } return self._qobj_model(**command_dict) @@ -332,7 +333,7 @@ def convert_set_phase(self, shift, instruction): command_dict = { "name": "setp", "t0": shift + instruction.start_time, - "ch": instruction.channel.name, + "ch": instruction.frame.name, "phase": instruction.phase, } return self._qobj_model(**command_dict) @@ -350,7 +351,7 @@ def convert_shift_phase(self, shift, instruction): command_dict = { "name": "fc", "t0": shift + instruction.start_time, - "ch": instruction.channel.name, + "ch": instruction.frame.name, "phase": instruction.phase, } return self._qobj_model(**command_dict) @@ -562,9 +563,10 @@ def convert_set_phase(self, instruction): """ t0 = instruction.t0 channel = self.get_channel(instruction.ch) + frame = Frame(channel.prefix, channel.index) phase = self.disassemble_value(instruction.phase) - return instructions.SetPhase(phase, channel) << t0 + return instructions.SetPhase(phase, frame) << t0 @bind_name("fc") def convert_shift_phase(self, instruction): @@ -577,9 +579,10 @@ def convert_shift_phase(self, instruction): """ t0 = instruction.t0 channel = self.get_channel(instruction.ch) + frame = Frame(channel.prefix, channel.index) phase = self.disassemble_value(instruction.phase) - return instructions.ShiftPhase(phase, channel) << t0 + return instructions.ShiftPhase(phase, frame) << t0 @bind_name("setf") def convert_set_frequency(self, instruction): @@ -594,9 +597,10 @@ def convert_set_frequency(self, instruction): """ t0 = instruction.t0 channel = self.get_channel(instruction.ch) + frame = Frame(channel.prefix, channel.index) frequency = self.disassemble_value(instruction.frequency) * GIGAHERTZ_TO_SI_UNITS - return instructions.SetFrequency(frequency, channel) << t0 + return instructions.SetFrequency(frequency, frame) << t0 @bind_name("shiftf") def convert_shift_frequency(self, instruction): @@ -612,9 +616,10 @@ def convert_shift_frequency(self, instruction): """ t0 = instruction.t0 channel = self.get_channel(instruction.ch) + frame = Frame(channel.prefix, channel.index) frequency = self.disassemble_value(instruction.frequency) * GIGAHERTZ_TO_SI_UNITS - return instructions.ShiftFrequency(frequency, channel) << t0 + return instructions.ShiftFrequency(frequency, frame) << t0 @bind_name("delay") def convert_delay(self, instruction): diff --git a/test/python/pulse/test_parameters.py b/test/python/pulse/test_parameters.py index 098c188da382..d99bc8f77971 100644 --- a/test/python/pulse/test_parameters.py +++ b/test/python/pulse/test_parameters.py @@ -65,17 +65,6 @@ def test_parameter_attribute_channel(self): chan = chan.assign(self.beta, 1) self.assertFalse(chan.is_parameterized()) - def test_parameter_attribute_instruction(self): - """Test the ``parameter`` attributes.""" - inst = pulse.ShiftFrequency(self.alpha * self.qubit, DriveChannel(self.qubit)) - self.assertTrue(inst.is_parameterized()) - self.assertEqual(inst.parameters, {self.alpha, self.qubit}) - inst.assign_parameters({self.alpha: self.qubit}) - self.assertEqual(inst.parameters, {self.qubit}) - inst.assign_parameters({self.qubit: 1}) - self.assertFalse(inst.is_parameterized()) - self.assertEqual(inst.parameters, set()) - def test_parameter_attribute_play(self): """Test the ``parameter`` attributes.""" inst = pulse.Play( @@ -194,7 +183,7 @@ def test_channels(self): schedule += pulse.ShiftPhase(self.phase, DriveChannel(2 * self.qubit)) schedule.assign_parameters({self.qubit: 4}) - self.assertEqual(schedule.instructions[0][1].channel, DriveChannel(8)) + self.assertEqual(schedule.instructions[0][1].frame, Frame("d", 8)) def test_acquire_channels(self): """Test Acquire instruction with multiple channels parameterized.""" From d18ba348d2f2512aa9c559c79dfe9f6a000ad579 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 29 Jun 2021 11:39:39 +0200 Subject: [PATCH 103/111] * Frame of channel can no longet be set by user. --- qiskit/pulse/channels.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/pulse/channels.py b/qiskit/pulse/channels.py index b60df7fd15f6..7d6a03d159a1 100644 --- a/qiskit/pulse/channels.py +++ b/qiskit/pulse/channels.py @@ -167,13 +167,12 @@ class PulseChannel(Channel, metaclass=ABCMeta): """Base class of transmit Channels. Pulses can be played on these channels.""" def __init__(self, index: int): - self._frame = Frame(self.prefix, index) super().__init__(index) @property def frame(self) -> Frame: """Return the default frame of this channel.""" - return self._frame + return Frame(self.prefix, self._index) class DriveChannel(PulseChannel): From 121fcb7e46ad3f4eda717b68782a69205e501346 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 29 Jun 2021 14:31:46 +0200 Subject: [PATCH 104/111] * Instructions have a frames property. * Shift/Set instructions can return a channel if the frame implies one. * Removed the ignore frames logic in the builder. --- qiskit/pulse/builder.py | 99 +++++++++---------- qiskit/pulse/frame.py | 5 + qiskit/pulse/instructions/acquire.py | 6 ++ qiskit/pulse/instructions/call.py | 6 ++ qiskit/pulse/instructions/delay.py | 6 ++ qiskit/pulse/instructions/directives.py | 11 +++ qiskit/pulse/instructions/frequency.py | 37 ++++++- qiskit/pulse/instructions/instruction.py | 11 ++- qiskit/pulse/instructions/phase.py | 41 ++++++-- qiskit/pulse/instructions/play.py | 5 + qiskit/pulse/instructions/snapshot.py | 6 ++ qiskit/pulse/schedule.py | 10 ++ qiskit/pulse/transforms/alignments.py | 63 ++---------- test/python/pulse/test_builder.py | 59 +++++------ test/python/pulse/test_frames.py | 61 +----------- .../pulse/test_instruction_schedule_map.py | 36 +++---- test/python/pulse/test_instructions.py | 40 +++++--- test/python/pulse/test_parameter_manager.py | 24 ++--- test/python/pulse/test_parameters.py | 30 +++--- test/python/pulse/test_schedule.py | 67 ++++++------- test/python/pulse/test_transforms.py | 4 + 21 files changed, 329 insertions(+), 298 deletions(-) diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index 9c6e6db28d9e..76ebbd2b2944 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -232,6 +232,7 @@ from qiskit.pulse.instructions import directives from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.pulse.transforms.alignments import AlignmentKind +from qiskit.pulse.frame import Frame #: contextvars.ContextVar[BuilderContext]: active builder BUILDER_CONTEXTVAR = contextvars.ContextVar("backend") @@ -873,9 +874,9 @@ def active_circuit_scheduler_settings() -> Dict[str, Any]: # pylint: disable=in # Contexts -# pylint: disable=unused-argument + @contextmanager -def align_left(ignore_frames: bool = False) -> ContextManager[None]: +def align_left() -> ContextManager[None]: """Left alignment pulse scheduling context. Pulse instructions within this context are scheduled as early as possible @@ -900,15 +901,11 @@ def align_left(ignore_frames: bool = False) -> ContextManager[None]: assert pulse_prog.ch_start_time(d0) == pulse_prog.ch_start_time(d1) - Args: - ignore_frames: If true then frame instructions will be ignored. This - should be set to true if the played Signals in this context - do not share any frames. Yields: None """ builder = _active_builder() - builder.push_context(transforms.AlignLeft(ignore_frames)) + builder.push_context(transforms.AlignLeft()) try: yield finally: @@ -916,9 +913,8 @@ def align_left(ignore_frames: bool = False) -> ContextManager[None]: builder.append_block(current) -# pylint: disable=unused-argument @contextmanager -def align_right(ignore_frames: bool = False) -> AlignmentKind: +def align_right() -> AlignmentKind: """Right alignment pulse scheduling context. Pulse instructions within this context are scheduled as late as possible @@ -943,16 +939,11 @@ def align_right(ignore_frames: bool = False) -> AlignmentKind: assert pulse_prog.ch_stop_time(d0) == pulse_prog.ch_stop_time(d1) - Args: - ignore_frames: If true then frame instructions will be ignore. This - should be set to true if the played Signals in this context - do not share any frames. - Yields: None """ builder = _active_builder() - builder.push_context(transforms.AlignRight(ignore_frames)) + builder.push_context(transforms.AlignRight()) try: yield finally: @@ -1249,7 +1240,7 @@ def circuit_scheduler_settings(**settings) -> ContextManager[None]: @contextmanager -def phase_offset(phase: float, *channels: chans.PulseChannel) -> ContextManager[None]: +def phase_offset(phase: float, *frames: Frame) -> ContextManager[None]: """Shift the phase of input channels on entry into context and undo on exit. Examples: @@ -1260,7 +1251,7 @@ def phase_offset(phase: float, *channels: chans.PulseChannel) -> ContextManager[ from qiskit import pulse - d0 = pulse.DriveChannel(0) + d0 = pulse.DriveChannel(0).frame with pulse.build() as pulse_prog: with pulse.phase_offset(math.pi, d0): @@ -1270,23 +1261,23 @@ def phase_offset(phase: float, *channels: chans.PulseChannel) -> ContextManager[ Args: phase: Amount of phase offset in radians. - channels: Channels to offset phase of. + frames: Channels to offset phase of. Yields: None """ - for channel in channels: - shift_phase(phase, channel) + for frame in frames: + shift_phase(phase, frame) try: yield finally: - for channel in channels: - shift_phase(-phase, channel) + for frame in frames: + shift_phase(-phase, frame) @contextmanager def frequency_offset( - frequency: float, *channels: chans.PulseChannel, compensate_phase: bool = False + frequency: float, *frames: Frame, compensate_phase: bool = False ) -> ContextManager[None]: """Shift the frequency of inputs channels on entry into context and undo on exit. @@ -1301,7 +1292,7 @@ def frequency_offset( with pulse.build(backend) as pulse_prog: # shift frequency by 1GHz - with pulse.frequency_offset(1e9, d0): + with pulse.frequency_offset(1e9, d0.frame): pulse.play(pulse.Constant(10, 1.0), d0) assert len(pulse_prog.instructions) == 3 @@ -1310,14 +1301,14 @@ def frequency_offset( # Shift frequency by 1GHz. # Undo accumulated phase in the shifted frequency frame # when exiting the context. - with pulse.frequency_offset(1e9, d0, compensate_phase=True): + with pulse.frequency_offset(1e9, d0.frame, compensate_phase=True): pulse.play(pulse.Constant(10, 1.0), d0) assert len(pulse_prog.instructions) == 4 Args: frequency: Amount of frequency offset in Hz. - channels: Channels to offset frequency of. + frames: Frames to offset frequency of. compensate_phase: Compensate for accumulated phase accumulated with respect to the channels' frame at its initial frequency. @@ -1331,8 +1322,8 @@ def frequency_offset( # offset context may change the t0 when the parent context is transformed. t0 = builder.get_context().duration - for channel in channels: - shift_frequency(frequency, channel) + for frame in frames: + shift_frequency(frequency, frame) try: yield finally: @@ -1340,11 +1331,11 @@ def frequency_offset( duration = builder.get_context().duration - t0 dt = active_backend().configuration().dt accumulated_phase = 2 * np.pi * ((duration * dt * frequency) % 1) - for channel in channels: - shift_phase(-accumulated_phase, channel) + for frame in frames: + shift_phase(-accumulated_phase, frame) - for channel in channels: - shift_frequency(-frequency, channel) + for frame in frames: + shift_frequency(-frequency, frame) # Channels @@ -1541,13 +1532,11 @@ def acquire( instructions.Acquire(duration, qubit_or_channel, reg_slot=register, **metadata) ) else: - raise exceptions.PulseError( - 'Register of type: "{}" is not supported'.format(type(register)) - ) + raise exceptions.PulseError(f'Register of type: "{type(register)}" is not supported') -def set_frequency(frequency: float, channel: chans.PulseChannel, name: Optional[str] = None): - """Set the ``frequency`` of a pulse ``channel``. +def set_frequency(frequency: float, frame: Frame, name: Optional[str] = None): + """Set the ``frequency`` of a ``frame``. Examples: @@ -1555,21 +1544,21 @@ def set_frequency(frequency: float, channel: chans.PulseChannel, name: Optional[ from qiskit import pulse - d0 = pulse.DriveChannel(0) + d0 = pulse.Frame("d", 0) with pulse.build() as pulse_prog: pulse.set_frequency(1e9, d0) Args: frequency: Frequency in Hz to set channel to. - channel: Channel to set frequency of. + frame: Channel to set frequency of. name: Name of the instruction. """ - append_instruction(instructions.SetFrequency(frequency, channel, name=name)) + append_instruction(instructions.SetFrequency(frequency, frame, name=name)) -def shift_frequency(frequency: float, channel: chans.PulseChannel, name: Optional[str] = None): - """Shift the ``frequency`` of a pulse ``channel``. +def shift_frequency(frequency: float, frame: Frame, name: Optional[str] = None): + """Shift the ``frequency`` of a ``frame``. Examples: @@ -1578,21 +1567,21 @@ def shift_frequency(frequency: float, channel: chans.PulseChannel, name: Optiona from qiskit import pulse - d0 = pulse.DriveChannel(0) + d0 = pulse.Frame("d", 0) with pulse.build() as pulse_prog: pulse.shift_frequency(1e9, d0) Args: frequency: Frequency in Hz to shift channel frequency by. - channel: Channel to shift frequency of. + frame: Frame to shift frequency of. name: Name of the instruction. """ - append_instruction(instructions.ShiftFrequency(frequency, channel, name=name)) + append_instruction(instructions.ShiftFrequency(frequency, frame, name=name)) -def set_phase(phase: float, channel: chans.PulseChannel, name: Optional[str] = None): - """Set the ``phase`` of a pulse ``channel``. +def set_phase(phase: float, frame: Frame, name: Optional[str] = None): + """Set the ``phase`` of a ``frame``. Examples: @@ -1603,21 +1592,21 @@ def set_phase(phase: float, channel: chans.PulseChannel, name: Optional[str] = N from qiskit import pulse - d0 = pulse.DriveChannel(0) + d0 = pulse.Frame("d", 0) with pulse.build() as pulse_prog: pulse.set_phase(math.pi, d0) Args: phase: Phase in radians to set channel carrier signal to. - channel: Channel to set phase of. + frame: Frame to set phase of. name: Name of the instruction. """ - append_instruction(instructions.SetPhase(phase, channel, name=name)) + append_instruction(instructions.SetPhase(phase, frame, name=name)) -def shift_phase(phase: float, channel: chans.PulseChannel, name: Optional[str] = None): - """Shift the ``phase`` of a pulse ``channel``. +def shift_phase(phase: float, frame: Frame, name: Optional[str] = None): + """Shift the ``phase`` of a ``frame``. Examples: @@ -1627,17 +1616,17 @@ def shift_phase(phase: float, channel: chans.PulseChannel, name: Optional[str] = from qiskit import pulse - d0 = pulse.DriveChannel(0) + d0 = pulse.Frame("d", 0) with pulse.build() as pulse_prog: pulse.shift_phase(math.pi, d0) Args: phase: Phase in radians to shift channel carrier signal by. - channel: Channel to shift phase of. + frame: Frame to shift phase of. name: Name of the instruction. """ - append_instruction(instructions.ShiftPhase(phase, channel, name)) + append_instruction(instructions.ShiftPhase(phase, frame, name)) def snapshot(label: str, snapshot_type: str = "statevector"): diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 739cbd83b7ab..990fefc05b5a 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -61,6 +61,11 @@ def prefix(self) -> str: """Return the prefix of the frame.""" return self._identifier[0] + @property + def index(self) -> Union[int, ParameterExpression]: + """Return the index of the frame.""" + return self._identifier[1] + @property def name(self) -> str: """Return the shorthand alias for this frame, which is based on its type and index.""" diff --git a/qiskit/pulse/instructions/acquire.py b/qiskit/pulse/instructions/acquire.py index f00e94172ff0..6b341a3ec5a8 100644 --- a/qiskit/pulse/instructions/acquire.py +++ b/qiskit/pulse/instructions/acquire.py @@ -20,6 +20,7 @@ from qiskit.pulse.configuration import Kernel, Discriminator from qiskit.pulse.exceptions import PulseError from qiskit.pulse.instructions.instruction import Instruction +from qiskit.pulse.frame import Frame class Acquire(Instruction): @@ -92,6 +93,11 @@ def channels(self) -> Tuple[Union[AcquireChannel, MemorySlot, RegisterSlot]]: """Returns the channels that this schedule uses.""" return tuple(self.operands[ind] for ind in (1, 2, 3) if self.operands[ind] is not None) + @property + def frames(self) -> Tuple[Frame]: + """Acquire does not act on frames so return an empty tuple.""" + return tuple() + @property def duration(self) -> Union[int, ParameterExpression]: """Duration of this instruction.""" diff --git a/qiskit/pulse/instructions/call.py b/qiskit/pulse/instructions/call.py index befe05aba345..851abb9d51dc 100644 --- a/qiskit/pulse/instructions/call.py +++ b/qiskit/pulse/instructions/call.py @@ -19,6 +19,7 @@ from qiskit.pulse.exceptions import PulseError from qiskit.pulse.instructions import instruction from qiskit.pulse.utils import format_parameter_value, deprecated_functionality +from qiskit.pulse.frame import Frame class Call(instruction.Instruction): @@ -83,6 +84,11 @@ def channels(self) -> Tuple[Channel]: """Returns the channels that this schedule uses.""" return self.assigned_subroutine().channels + @property + def frames(self) -> Tuple[Frame]: + """Returns the frames that this schedule uses.""" + return self.assigned_subroutine().frames + # pylint: disable=missing-return-type-doc @property def subroutine(self): diff --git a/qiskit/pulse/instructions/delay.py b/qiskit/pulse/instructions/delay.py index f045a4880ff3..64977deb0819 100644 --- a/qiskit/pulse/instructions/delay.py +++ b/qiskit/pulse/instructions/delay.py @@ -16,6 +16,7 @@ from qiskit.circuit import ParameterExpression from qiskit.pulse.channels import Channel from qiskit.pulse.instructions.instruction import Instruction +from qiskit.pulse.frame import Frame class Delay(Instruction): @@ -63,6 +64,11 @@ def channels(self) -> Tuple[Channel]: """Returns the channels that this schedule uses.""" return (self.channel,) + @property + def frames(self) -> Tuple[Frame]: + """Delay does not apply on frames.""" + return tuple() + @property def duration(self) -> Union[int, ParameterExpression]: """Duration of this instruction.""" diff --git a/qiskit/pulse/instructions/directives.py b/qiskit/pulse/instructions/directives.py index d09b69823332..fd49c0ed2e4a 100644 --- a/qiskit/pulse/instructions/directives.py +++ b/qiskit/pulse/instructions/directives.py @@ -17,6 +17,7 @@ from qiskit.pulse import channels as chans from qiskit.pulse.instructions import instruction +from qiskit.pulse.frame import Frame class Directive(instruction.Instruction, ABC): @@ -52,6 +53,16 @@ def channels(self) -> Tuple[chans.Channel]: """Returns the channels that this schedule uses.""" return self.operands + @property + def frames(self) -> Tuple[Frame]: + """Returns the frames that this schedule uses.""" + frames = set() + for chan in self.channels: + if isinstance(chan, chans.PulseChannel): + frames.update(chan.frame) + + return tuple(frames) + def __eq__(self, other): """Verify two barriers are equivalent.""" return isinstance(other, type(self)) and set(self.channels) == set(other.channels) diff --git a/qiskit/pulse/instructions/frequency.py b/qiskit/pulse/instructions/frequency.py index 2b7cfae1e784..3dea42cdc58b 100644 --- a/qiskit/pulse/instructions/frequency.py +++ b/qiskit/pulse/instructions/frequency.py @@ -17,10 +17,9 @@ import warnings from qiskit.circuit.parameterexpression import ParameterExpression -from qiskit.pulse.channels import PulseChannel +from qiskit.pulse.channels import PulseChannel, DriveChannel , MeasureChannel, ControlChannel from qiskit.pulse.frame import Frame from qiskit.pulse.instructions.instruction import Instruction -from qiskit.pulse.exceptions import PulseError class SetFrequency(Instruction): @@ -72,7 +71,14 @@ def channel(self) -> PulseChannel: """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ - raise PulseError(f"{self} applies to a {type(self.operands[1])}") + if self.frame.prefix == "d": + return DriveChannel(self.frame.index) + + if self.frame.prefix == "m": + return MeasureChannel(self.frame.index) + + if self.frame.prefix == "u": + return ControlChannel(self.frame.index) @property def frame(self) -> Frame: @@ -82,8 +88,16 @@ def frame(self) -> Frame: @property def channels(self) -> Tuple[PulseChannel]: """Returns the channels that this schedule uses.""" + if self.channel is not None: + return (self.channel,) + return tuple() + @property + def frames(self) -> Tuple[Frame]: + """Return the frames this instructions acts on.""" + return (self.frame, ) + @property def duration(self) -> int: """Duration of this instruction.""" @@ -132,7 +146,14 @@ def channel(self) -> PulseChannel: """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ - raise PulseError(f"{self} applies to a {type(self.operands[1])}") + if self.frame.prefix == "d": + return DriveChannel(self.frame.index) + + if self.frame.prefix == "m": + return MeasureChannel(self.frame.index) + + if self.frame.prefix == "u": + return ControlChannel(self.frame.index) @property def frame(self) -> Frame: @@ -142,8 +163,16 @@ def frame(self) -> Frame: @property def channels(self) -> Tuple[PulseChannel]: """Returns the channels that this schedule uses.""" + if self.channel is not None: + return (self.channel,) + return tuple() + @property + def frames(self) -> Tuple[Frame]: + """Return the frames this instructions acts on.""" + return (self.frame, ) + @property def duration(self) -> int: """Duration of this instruction.""" diff --git a/qiskit/pulse/instructions/instruction.py b/qiskit/pulse/instructions/instruction.py index 986321519844..bc78633914f7 100644 --- a/qiskit/pulse/instructions/instruction.py +++ b/qiskit/pulse/instructions/instruction.py @@ -22,7 +22,7 @@ sched += Delay(duration, channel) # Delay is a specific subclass of Instruction """ import warnings -from abc import ABC, abstractproperty +from abc import ABC, abstractmethod from collections import defaultdict from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, Any, Union @@ -103,11 +103,18 @@ def operands(self) -> Tuple: """Return instruction operands.""" return self._operands - @abstractproperty + @property + @abstractmethod def channels(self) -> Tuple[Channel]: """Returns the channels that this schedule uses.""" raise NotImplementedError + @property + @abstractmethod + def frames(self) -> Tuple[Frame]: + """Returns the frames that this instruction uses.""" + raise NotImplementedError + @property def start_time(self) -> int: """Relative begin time of this instruction.""" diff --git a/qiskit/pulse/instructions/phase.py b/qiskit/pulse/instructions/phase.py index b7dd2a933056..fd8db734ad30 100644 --- a/qiskit/pulse/instructions/phase.py +++ b/qiskit/pulse/instructions/phase.py @@ -19,10 +19,9 @@ import warnings from qiskit.circuit import ParameterExpression -from qiskit.pulse.channels import PulseChannel +from qiskit.pulse.channels import PulseChannel, DriveChannel, MeasureChannel, ControlChannel from qiskit.pulse.frame import Frame from qiskit.pulse.instructions.instruction import Instruction -from qiskit.pulse.exceptions import PulseError class ShiftPhase(Instruction): @@ -72,11 +71,18 @@ def phase(self) -> Union[complex, ParameterExpression]: return self.operands[0] @property - def channel(self) -> Union[PulseChannel, Frame]: + def channel(self) -> PulseChannel: """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ - raise PulseError(f"{self} applies to a {type(self.operands[1])}") + if self.frame.prefix == "d": + return DriveChannel(self.frame.index) + + if self.frame.prefix == "m": + return MeasureChannel(self.frame.index) + + if self.frame.prefix == "u": + return ControlChannel(self.frame.index) @property def frame(self) -> Frame: @@ -86,8 +92,16 @@ def frame(self) -> Frame: @property def channels(self) -> Tuple[PulseChannel]: """Returns the channels that this schedule uses.""" + if self.channel is not None: + return (self.channel, ) + return tuple() + @property + def frames(self) -> Tuple[Frame]: + """Return the frames this instructions acts on.""" + return (self.frame, ) + @property def duration(self) -> int: """Duration of this instruction.""" @@ -141,11 +155,18 @@ def phase(self) -> Union[complex, ParameterExpression]: return self.operands[0] @property - def channel(self) -> Union[PulseChannel, Frame]: + def channel(self) -> PulseChannel: """Return the :py:class:`~qiskit.pulse.channels.Channel` or frame that this instruction is scheduled on. """ - raise PulseError(f"{self} applies to a {type(self.operands[1])}") + if self.frame.prefix == "d": + return DriveChannel(self.frame.index) + + if self.frame.prefix == "m": + return MeasureChannel(self.frame.index) + + if self.frame.prefix == "u": + return ControlChannel(self.frame.index) @property def frame(self) -> Frame: @@ -155,8 +176,16 @@ def frame(self) -> Frame: @property def channels(self) -> Tuple[PulseChannel]: """Returns the channels that this schedule uses.""" + if self.channel is not None: + return (self.channel,) + return tuple() + @property + def frames(self) -> Tuple[Frame]: + """Return the frames this instructions acts on.""" + return (self.frame, ) + @property def duration(self) -> int: """Duration of this instruction.""" diff --git a/qiskit/pulse/instructions/play.py b/qiskit/pulse/instructions/play.py index 0f3cae466b29..f3b84ba08aac 100644 --- a/qiskit/pulse/instructions/play.py +++ b/qiskit/pulse/instructions/play.py @@ -98,6 +98,11 @@ def channels(self) -> Tuple[PulseChannel]: """Returns the channels that this schedule uses.""" return (self.channel,) + @property + def frames(self) -> Tuple[Frame]: + """Return the frames this instructions acts on.""" + return (self.frame, ) + @property def duration(self) -> Union[int, ParameterExpression]: """Duration of this instruction.""" diff --git a/qiskit/pulse/instructions/snapshot.py b/qiskit/pulse/instructions/snapshot.py index a4b09f7e2155..0de375372718 100644 --- a/qiskit/pulse/instructions/snapshot.py +++ b/qiskit/pulse/instructions/snapshot.py @@ -18,6 +18,7 @@ from qiskit.pulse.channels import SnapshotChannel from qiskit.pulse.exceptions import PulseError from qiskit.pulse.instructions.instruction import Instruction +from qiskit.pulse.frame import Frame class Snapshot(Instruction): @@ -65,6 +66,11 @@ def channels(self) -> Tuple[SnapshotChannel]: """Returns the channels that this schedule uses.""" return (self.channel,) + @property + def frames(self) -> Tuple[Frame]: + """Return the frames this instructions acts on.""" + return tuple() + @property def duration(self) -> int: """Duration of this instruction.""" diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py index 387eac2862e9..6470ff23f97c 100644 --- a/qiskit/pulse/schedule.py +++ b/qiskit/pulse/schedule.py @@ -35,6 +35,7 @@ from qiskit.pulse.instructions import Instruction from qiskit.pulse.utils import instruction_duration_validation, deprecated_functionality from qiskit.utils.multiprocessing import is_main_process +from qiskit.pulse.frame import Frame Interval = Tuple[int, int] @@ -232,6 +233,15 @@ def channels(self) -> Tuple[Channel]: """Returns channels that this schedule uses.""" return tuple(self._timeslots.keys()) + @property + def frames(self) -> Tuple[Frame]: + """Returns the frames this schedule uses.""" + frames = {} + for _, inst in self.instructions: + frames = frames | inst.frames + + return tuple(frames) + @property def _children(self) -> Tuple[Tuple[int, ScheduleComponent], ...]: """Deprecated. Return the child schedule components of this ``Schedule`` in the diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py index 120f571c62b4..0f3bbe71cf95 100644 --- a/qiskit/pulse/transforms/alignments.py +++ b/qiskit/pulse/transforms/alignments.py @@ -19,7 +19,6 @@ from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.pulse.exceptions import PulseError from qiskit.pulse.schedule import Schedule, ScheduleComponent -from qiskit.pulse.frame import Frame from qiskit.pulse.utils import instruction_duration_validation, deprecated_functionality @@ -71,19 +70,6 @@ class AlignLeft(AlignmentKind): is_sequential = False - def __init__(self, ignore_frames: bool = False): - """ - Left-alignment context. - - Args: - ignore_frames: If true then frame instructions will be ignored. This - should be set to true if the played Signals in this context - do not share any frames. - """ - super().__init__() - - self._ignore_frames = ignore_frames - def align(self, schedule: Schedule) -> Schedule: """Reallocate instructions according to the policy. @@ -102,7 +88,8 @@ def align(self, schedule: Schedule) -> Schedule: return aligned - def _push_left_append(self, this: Schedule, other: ScheduleComponent) -> Schedule: + @staticmethod + def _push_left_append(this: Schedule, other: ScheduleComponent) -> Schedule: """Return ``this`` with ``other`` inserted at the maximum time over all channels shared between ```this`` and ``other``. @@ -116,14 +103,6 @@ def _push_left_append(self, this: Schedule, other: ScheduleComponent) -> Schedul this_channels = set(this.channels) other_channels = set(other.channels) shared_channels = list(this_channels & other_channels) - - # Conservatively assume that a Frame instruction could impact all channels - if not self._ignore_frames: - for ch in this_channels | other_channels: - if isinstance(ch, Frame): - shared_channels = list(this_channels | other_channels) - break - ch_slacks = [ this.stop_time - this.ch_stop_time(channel) + other.ch_start_time(channel) for channel in shared_channels @@ -152,19 +131,6 @@ class AlignRight(AlignmentKind): is_sequential = False - def __init__(self, ignore_frames: bool = False): - """ - Right-alignment context. - - Args: - ignore_frames: If true then frame instructions will be ignore. This - should be set to true if the played Signals in this context - do not share any frames. - """ - super().__init__() - - self._ignore_frames = ignore_frames - def align(self, schedule: Schedule) -> Schedule: """Reallocate instructions according to the policy. @@ -183,7 +149,8 @@ def align(self, schedule: Schedule) -> Schedule: return aligned - def _push_right_prepend(self, this: ScheduleComponent, other: ScheduleComponent) -> Schedule: + @staticmethod + def _push_right_prepend(this: ScheduleComponent, other: ScheduleComponent) -> Schedule: """Return ``this`` with ``other`` inserted at the latest possible time such that ``other`` ends before it overlaps with any of ``this``. @@ -200,14 +167,6 @@ def _push_right_prepend(self, this: ScheduleComponent, other: ScheduleComponent) this_channels = set(this.channels) other_channels = set(other.channels) shared_channels = list(this_channels & other_channels) - - # Conservatively assume that a Frame instruction could impact all channels - if not self._ignore_frames: - for ch in this_channels | other_channels: - if isinstance(ch, Frame): - shared_channels = list(this_channels | other_channels) - break - ch_slacks = [ this.ch_start_time(channel) - other.ch_stop_time(channel) for channel in shared_channels ] @@ -408,38 +367,32 @@ def to_dict(self) -> Dict[str, Any]: @deprecated_functionality -def align_left(schedule: Schedule, ignore_frames: bool = False) -> Schedule: +def align_left(schedule: Schedule) -> Schedule: """Align a list of pulse instructions on the left. Args: schedule: Input schedule of which top-level sub-schedules will be rescheduled. - ignore_frames: If true then frame instructions will be ignore. This - should be set to true if the played Signals in this context - do not share any frames. Returns: New schedule with input `schedule`` child schedules and instructions left aligned. """ - context = AlignLeft(ignore_frames) + context = AlignLeft() return context.align(schedule) @deprecated_functionality -def align_right(schedule: Schedule, ignore_frames: bool = False) -> Schedule: +def align_right(schedule: Schedule) -> Schedule: """Align a list of pulse instructions on the right. Args: schedule: Input schedule of which top-level sub-schedules will be rescheduled. - ignore_frames: If true then frame instructions will be ignore. This - should be set to true if the played Signals in this context - do not share any frames. Returns: New schedule with input `schedule`` child schedules and instructions right aligned. """ - context = AlignRight(ignore_frames) + context = AlignRight() return context.align(schedule) diff --git a/test/python/pulse/test_builder.py b/test/python/pulse/test_builder.py index beb732046326..b8870a16135d 100644 --- a/test/python/pulse/test_builder.py +++ b/test/python/pulse/test_builder.py @@ -249,13 +249,13 @@ def test_phase_offset(self): d0 = pulse.DriveChannel(0) with pulse.build() as schedule: - with pulse.phase_offset(3.14, d0): + with pulse.phase_offset(3.14, d0.frame): pulse.delay(10, d0) reference = pulse.Schedule() - reference += instructions.ShiftPhase(3.14, d0) + reference += instructions.ShiftPhase(3.14, d0.frame) reference += instructions.Delay(10, d0) - reference += instructions.ShiftPhase(-3.14, d0) + reference += instructions.ShiftPhase(-3.14, d0.frame) self.assertScheduleEqual(schedule, reference) @@ -264,13 +264,13 @@ def test_frequency_offset(self): d0 = pulse.DriveChannel(0) with pulse.build() as schedule: - with pulse.frequency_offset(1e9, d0): + with pulse.frequency_offset(1e9, d0.frame): pulse.delay(10, d0) reference = pulse.Schedule() - reference += instructions.ShiftFrequency(1e9, d0) + reference += instructions.ShiftFrequency(1e9, d0.frame) reference += instructions.Delay(10, d0) - reference += instructions.ShiftFrequency(-1e9, d0) + reference += instructions.ShiftFrequency(-1e9, d0.frame) self.assertScheduleEqual(schedule, reference) @@ -280,16 +280,16 @@ def test_phase_compensated_frequency_offset(self): d0 = pulse.DriveChannel(0) with pulse.build(self.backend) as schedule: - with pulse.frequency_offset(1e9, d0, compensate_phase=True): + with pulse.frequency_offset(1e9, d0.frame, compensate_phase=True): pulse.delay(10, d0) reference = pulse.Schedule() - reference += instructions.ShiftFrequency(1e9, d0) + reference += instructions.ShiftFrequency(1e9, d0.frame) reference += instructions.Delay(10, d0) reference += instructions.ShiftPhase( - -2 * np.pi * ((1e9 * 10 * self.configuration.dt) % 1), d0 + -2 * np.pi * ((1e9 * 10 * self.configuration.dt) % 1), d0.frame ) - reference += instructions.ShiftFrequency(-1e9, d0) + reference += instructions.ShiftFrequency(-1e9, d0.frame) self.assertScheduleEqual(schedule, reference) @@ -424,10 +424,9 @@ def test_acquire_qubit(self): def test_instruction_name_argument(self): """Test setting the name of an instruction.""" - d0 = pulse.DriveChannel(0) + d0 = pulse.Frame("d", 0) for instruction_method in [ - pulse.delay, pulse.set_frequency, pulse.set_phase, pulse.shift_frequency, @@ -437,9 +436,13 @@ def test_instruction_name_argument(self): instruction_method(0, d0, name="instruction_name") self.assertEqual(schedule.instructions[0][1].name, "instruction_name") + with pulse.build() as schedule: + pulse.delay(0, d0, name="instruction_name") + self.assertEqual(schedule.instructions[0][1].name, "instruction_name") + def test_set_frequency(self): """Test set frequency instruction.""" - d0 = pulse.DriveChannel(0) + d0 = pulse.Frame("d", 0) with pulse.build() as schedule: pulse.set_frequency(1e9, d0) @@ -451,7 +454,7 @@ def test_set_frequency(self): def test_shift_frequency(self): """Test shift frequency instruction.""" - d0 = pulse.DriveChannel(0) + d0 = pulse.Frame("d", 0) with pulse.build() as schedule: pulse.shift_frequency(0.1e9, d0) @@ -463,7 +466,7 @@ def test_shift_frequency(self): def test_set_phase(self): """Test set phase instruction.""" - d0 = pulse.DriveChannel(0) + d0 = pulse.Frame("d", 0) with pulse.build() as schedule: pulse.set_phase(3.14, d0) @@ -475,7 +478,7 @@ def test_set_phase(self): def test_shift_phase(self): """Test shift phase instruction.""" - d0 = pulse.DriveChannel(0) + d0 = pulse.Frame("d", 0) with pulse.build() as schedule: pulse.shift_phase(3.14, d0) @@ -1235,31 +1238,31 @@ class TestBuilderFrames(TestBuilder): def test_simple_frame_schedule(self): """Test basic schedule construction with frames.""" - signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q", 0)) + signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("d", 0)) with pulse.build() as sched: with pulse.align_left(): pulse.play(signal, pulse.DriveChannel(0)) - pulse.shift_phase(1.57, pulse.Frame("Q", 0)) + pulse.shift_phase(1.57, pulse.Frame("d", 0)) pulse.play(signal, pulse.DriveChannel(0)) play_gaus = pulse.Play(signal, pulse.DriveChannel(0)) self.assertEqual(sched.instructions[0][0], 0) self.assertEqual(sched.instructions[0][1], play_gaus) self.assertEqual(sched.instructions[1][0], 160) - self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame("Q", 0))) + self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame("d", 0))) self.assertEqual(sched.instructions[2][0], 160) self.assertEqual(sched.instructions[2][1], play_gaus) def test_ignore_frames(self): """Test the behavior of ignoring frames in the alignments context.""" - signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q", 0)) + signal = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("d", 0)) with pulse.build() as sched: - with pulse.align_right(ignore_frames=True): + with pulse.align_right(): pulse.play(signal, pulse.DriveChannel(0)) - pulse.shift_phase(1.57, pulse.Frame("Q", 0)) - pulse.shift_phase(1.57, pulse.Frame("Q", 1)) + pulse.shift_phase(1.57, pulse.Frame("d", 0)) + pulse.shift_phase(1.57, pulse.Frame("d", 1)) pulse.play(signal, pulse.DriveChannel(0)) play_gaus = pulse.Play(signal, pulse.DriveChannel(0)) @@ -1267,14 +1270,14 @@ def test_ignore_frames(self): self.assertEqual(sched.instructions[0][0], 0) self.assertEqual(sched.instructions[0][1], play_gaus) self.assertEqual(sched.instructions[1][0], 160) - self.assertEqual(sched.instructions[1][1], play_gaus) - self.assertEqual(sched.instructions[2][0], 320) - self.assertEqual(sched.instructions[2][1], pulse.ShiftPhase(1.57, pulse.Frame("Q", 0))) + self.assertEqual(sched.instructions[1][1], pulse.ShiftPhase(1.57, pulse.Frame("d", 0))) + self.assertEqual(sched.instructions[2][0], 160) + self.assertEqual(sched.instructions[2][1], play_gaus) self.assertEqual(sched.instructions[3][0], 320) - self.assertEqual(sched.instructions[3][1], pulse.ShiftPhase(1.57, pulse.Frame("Q", 1))) + self.assertEqual(sched.instructions[3][1], pulse.ShiftPhase(1.57, pulse.Frame("d", 1))) with pulse.build() as sched: - with pulse.align_right(ignore_frames=False): + with pulse.align_sequential(): pulse.play(signal, pulse.DriveChannel(0)) pulse.shift_phase(1.57, pulse.Frame("Q", 0)) pulse.shift_phase(1.57, pulse.Frame("Q", 1)) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index ed3f44725be4..904f105a3e24 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -248,8 +248,9 @@ def test_phase_advance_with_instructions(self): sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q", 0)) with pulse.build() as sched: - pulse.play(sig, pulse.DriveChannel(0)) - pulse.shift_phase(1.0, pulse.Frame("Q", 0)) + with pulse.align_sequential(): + pulse.play(sig, pulse.DriveChannel(0)) + pulse.shift_phase(1.0, pulse.Frame("Q", 0)) frame = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq0), self.dt_) frame.set_frame_instructions(block_to_schedule(sched)) @@ -280,62 +281,6 @@ def test_set_frequency(self): self.assertAlmostEqual(frame.frequency(15), self.freq1, places=8) self.assertAlmostEqual(frame.frequency(16), self.freq0, places=8) - def test_broadcasting(self): - """Test that resolved frames broadcast to control channels.""" - - sig = pulse.Signal(pulse.Gaussian(160, 0.1, 40), pulse.Frame("Q", 0)) - - with pulse.build() as sched: - with pulse.align_left(): - pulse.play(sig, pulse.DriveChannel(3)) - pulse.shift_phase(1.23, pulse.Frame("Q", 0)) - with pulse.align_left(ignore_frames=True): - pulse.play(sig, pulse.DriveChannel(3)) - pulse.play(sig, pulse.ControlChannel(0)) - - frames_config = FramesConfiguration.from_dict({ - pulse.Frame("Q", 0): { - "frequency": self.freq0, - "purpose": "Frame of qubit 0.", - } - }) - frames_config.sample_duration = self.dt_ - - resolved = resolve_frames(sched, frames_config).instructions - - phase = (np.angle(np.exp(2.0j * np.pi * self.freq0 * 160 * self.dt_)) + 1.23) % (2 * np.pi) - - # Check that the frame resolved schedule has the correct phase and frequency instructions - # at the right place. - # First, ensure that resolved starts with a SetFrequency and SetPhase. - self.assertEqual(resolved[0][0], 0) - set_freq = resolved[0][1] - self.assertTrue(isinstance(set_freq, pulse.SetFrequency)) - self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) - - self.assertEqual(resolved[1][0], 0) - play_pulse = resolved[1][1] - self.assertTrue(isinstance(play_pulse, pulse.Play)) - - # Next, check that we do phase shifts on the DriveChannel after the first Gaussian. - self.assertEqual(resolved[2][0], 160) - shift_phase = resolved[2][1] - self.assertTrue(isinstance(shift_phase, pulse.ShiftPhase)) - self.assertAlmostEqual(shift_phase.phase, 1.23, places=8) - - # Up to now, no pulse has been applied on the ControlChannel so we should - # encounter a Set instructions at time 160 which is when the first pulse - # is played on ControlChannel(0) - self.assertEqual(resolved[3][0], 160) - set_freq = resolved[3][1] - self.assertTrue(isinstance(set_freq, pulse.SetFrequency)) - self.assertAlmostEqual(set_freq.frequency, self.freq0, places=8) - - self.assertEqual(resolved[4][0], 160) - set_phase = resolved[4][1] - self.assertTrue(isinstance(set_phase, pulse.SetPhase)) - self.assertAlmostEqual(set_phase.phase % (2 * np.pi), phase, places=8) - def test_implicit_frame(self): """If a frame is not specified then the frame of the channel is assumed.""" diff --git a/test/python/pulse/test_instruction_schedule_map.py b/test/python/pulse/test_instruction_schedule_map.py index 70148f2b791c..21660d9e74c0 100644 --- a/test/python/pulse/test_instruction_schedule_map.py +++ b/test/python/pulse/test_instruction_schedule_map.py @@ -392,17 +392,17 @@ def test_schedule_with_non_alphanumeric_ordering(self): lamb = Parameter("lambda") target_sched = Schedule() - target_sched.insert(0, ShiftPhase(theta, DriveChannel(0)), inplace=True) - target_sched.insert(10, ShiftPhase(phi, DriveChannel(0)), inplace=True) - target_sched.insert(20, ShiftPhase(lamb, DriveChannel(0)), inplace=True) + target_sched.insert(0, ShiftPhase(theta, DriveChannel(0).frame), inplace=True) + target_sched.insert(10, ShiftPhase(phi, DriveChannel(0).frame), inplace=True) + target_sched.insert(20, ShiftPhase(lamb, DriveChannel(0).frame), inplace=True) inst_map = InstructionScheduleMap() inst_map.add("target_sched", (0,), target_sched, arguments=["theta", "phi", "lambda"]) ref_sched = Schedule() - ref_sched.insert(0, ShiftPhase(0, DriveChannel(0)), inplace=True) - ref_sched.insert(10, ShiftPhase(1, DriveChannel(0)), inplace=True) - ref_sched.insert(20, ShiftPhase(2, DriveChannel(0)), inplace=True) + ref_sched.insert(0, ShiftPhase(0, DriveChannel(0).frame), inplace=True) + ref_sched.insert(10, ShiftPhase(1, DriveChannel(0).frame), inplace=True) + ref_sched.insert(20, ShiftPhase(2, DriveChannel(0).frame), inplace=True) # if parameter is alphanumerical ordering this maps to # theta -> 2 @@ -423,7 +423,7 @@ def test_binding_too_many_parameters(self): param = Parameter("param") target_sched = Schedule() - target_sched.insert(0, ShiftPhase(param, DriveChannel(0)), inplace=True) + target_sched.insert(0, ShiftPhase(param, DriveChannel(0).frame), inplace=True) inst_map = InstructionScheduleMap() inst_map.add("target_sched", (0,), target_sched) @@ -436,7 +436,7 @@ def test_binding_unassigned_parameters(self): param = Parameter("param") target_sched = Schedule() - target_sched.insert(0, ShiftPhase(param, DriveChannel(0)), inplace=True) + target_sched.insert(0, ShiftPhase(param, DriveChannel(0).frame), inplace=True) inst_map = InstructionScheduleMap() inst_map.add("target_sched", (0,), target_sched) @@ -451,17 +451,17 @@ def test_schedule_with_multiple_parameters_under_same_name(self): param3 = Parameter("param") target_sched = Schedule() - target_sched.insert(0, ShiftPhase(param1, DriveChannel(0)), inplace=True) - target_sched.insert(10, ShiftPhase(param2, DriveChannel(0)), inplace=True) - target_sched.insert(20, ShiftPhase(param3, DriveChannel(0)), inplace=True) + target_sched.insert(0, ShiftPhase(param1, DriveChannel(0).frame), inplace=True) + target_sched.insert(10, ShiftPhase(param2, DriveChannel(0).frame), inplace=True) + target_sched.insert(20, ShiftPhase(param3, DriveChannel(0).frame), inplace=True) inst_map = InstructionScheduleMap() inst_map.add("target_sched", (0,), target_sched) ref_sched = Schedule() - ref_sched.insert(0, ShiftPhase(1.23, DriveChannel(0)), inplace=True) - ref_sched.insert(10, ShiftPhase(1.23, DriveChannel(0)), inplace=True) - ref_sched.insert(20, ShiftPhase(1.23, DriveChannel(0)), inplace=True) + ref_sched.insert(0, ShiftPhase(1.23, DriveChannel(0).frame), inplace=True) + ref_sched.insert(10, ShiftPhase(1.23, DriveChannel(0).frame), inplace=True) + ref_sched.insert(20, ShiftPhase(1.23, DriveChannel(0).frame), inplace=True) test_sched = inst_map.get("target_sched", (0,), param=1.23) @@ -475,15 +475,15 @@ def test_get_schedule_with_unbound_parameter(self): param2 = Parameter("param2") target_sched = Schedule() - target_sched.insert(0, ShiftPhase(param1, DriveChannel(0)), inplace=True) - target_sched.insert(10, ShiftPhase(param2, DriveChannel(0)), inplace=True) + target_sched.insert(0, ShiftPhase(param1, DriveChannel(0).frame), inplace=True) + target_sched.insert(10, ShiftPhase(param2, DriveChannel(0).frame), inplace=True) inst_map = InstructionScheduleMap() inst_map.add("target_sched", (0,), target_sched) ref_sched = Schedule() - ref_sched.insert(0, ShiftPhase(param1, DriveChannel(0)), inplace=True) - ref_sched.insert(10, ShiftPhase(1.23, DriveChannel(0)), inplace=True) + ref_sched.insert(0, ShiftPhase(param1, DriveChannel(0).frame), inplace=True) + ref_sched.insert(10, ShiftPhase(1.23, DriveChannel(0).frame), inplace=True) test_sched = inst_map.get("target_sched", (0,), param2=1.23) diff --git a/test/python/pulse/test_instructions.py b/test/python/pulse/test_instructions.py index 57472cf16fa5..71f102acd2c9 100644 --- a/test/python/pulse/test_instructions.py +++ b/test/python/pulse/test_instructions.py @@ -134,25 +134,33 @@ class TestSetFrequency(QiskitTestCase): def test_freq(self): """Test set frequency basic functionality.""" - set_freq = instructions.SetFrequency(4.5e9, channels.DriveChannel(1), name="test") + set_freq = instructions.SetFrequency(4.5e9, channels.DriveChannel(1).frame, name="test") self.assertIsInstance(set_freq.id, int) self.assertEqual(set_freq.duration, 0) self.assertEqual(set_freq.frequency, 4.5e9) - self.assertEqual(set_freq.operands, (4.5e9, channels.DriveChannel(1))) + self.assertEqual(set_freq.operands, (4.5e9, channels.DriveChannel(1).frame)) self.assertEqual( - set_freq, instructions.SetFrequency(4.5e9, channels.DriveChannel(1), name="test") + set_freq, instructions.SetFrequency(4.5e9, Frame("d", 1), name="test") ) self.assertNotEqual( - set_freq, instructions.SetFrequency(4.5e8, channels.DriveChannel(1), name="test") + set_freq, instructions.SetFrequency(4.5e8, Frame("d", 1), name="test") ) - self.assertEqual(repr(set_freq), "SetFrequency(4500000000.0, DriveChannel(1), name='test')") + self.assertEqual(repr(set_freq), "SetFrequency(4500000000.0, Frame(d1), name='test')") def test_frame(self): """Test the basic shift phase on a Frame.""" set_freq = instructions.SetFrequency(4.5e9, Frame("Q", 123)) - self.assertEqual(set_freq.channel, Frame("Q", 123)) + self.assertEqual(set_freq.channel, None) + self.assertEqual(set_freq.frame, Frame("Q", 123)) + + def test_native_frame(self): + """Test the basic shift phase on a Frame.""" + set_freq = instructions.SetFrequency(4.5e9, Frame("m", 123)) + + self.assertEqual(set_freq.channel, channels.MeasureChannel(123)) + self.assertEqual(set_freq.frame, Frame("m", 123)) class TestShiftPhase(QiskitTestCase): @@ -160,26 +168,34 @@ class TestShiftPhase(QiskitTestCase): def test_default(self): """Test basic ShiftPhase.""" - shift_phase = instructions.ShiftPhase(1.57, channels.DriveChannel(0)) + shift_phase = instructions.ShiftPhase(1.57, channels.DriveChannel(0).frame) self.assertIsInstance(shift_phase.id, int) self.assertEqual(shift_phase.name, None) self.assertEqual(shift_phase.duration, 0) self.assertEqual(shift_phase.phase, 1.57) - self.assertEqual(shift_phase.operands, (1.57, channels.DriveChannel(0))) + self.assertEqual(shift_phase.operands, (1.57, channels.DriveChannel(0).frame)) self.assertEqual( - shift_phase, instructions.ShiftPhase(1.57, channels.DriveChannel(0), name="test") + shift_phase, instructions.ShiftPhase(1.57, Frame("d", 0), name="test") ) self.assertNotEqual( - shift_phase, instructions.ShiftPhase(1.57j, channels.DriveChannel(0), name="test") + shift_phase, instructions.ShiftPhase(1.57j, Frame("d", 0), name="test") ) - self.assertEqual(repr(shift_phase), "ShiftPhase(1.57, DriveChannel(0))") + self.assertEqual(repr(shift_phase), "ShiftPhase(1.57, Frame(d0))") def test_frame(self): """Test the basic shift phase on a Frame.""" shift_phase = instructions.ShiftPhase(1.57, Frame("Q", 123)) - self.assertEqual(shift_phase.channel, Frame("Q", 123)) + self.assertEqual(shift_phase.channel, None) + self.assertEqual(shift_phase.frame, Frame("Q", 123)) + + def test_native_frame(self): + """Test the basic shift phase on a Frame.""" + shift_phase = instructions.ShiftPhase(1.57, Frame("d", 123)) + + self.assertEqual(shift_phase.channel, pulse.DriveChannel(123)) + self.assertEqual(shift_phase.frame, Frame("d", 123)) class TestSnapshot(QiskitTestCase): diff --git a/test/python/pulse/test_parameter_manager.py b/test/python/pulse/test_parameter_manager.py index 615b5f309474..cc87c07992a3 100644 --- a/test/python/pulse/test_parameter_manager.py +++ b/test/python/pulse/test_parameter_manager.py @@ -71,18 +71,18 @@ def setUp(self): # schedule under test subroutine = pulse.ScheduleBlock(alignment_context=AlignLeft()) - subroutine += pulse.ShiftPhase(self.phi1, self.d1) + subroutine += pulse.ShiftPhase(self.phi1, self.d1.frame) subroutine += pulse.Play(self.parametric_waveform1, self.d1) sched = pulse.Schedule() - sched += pulse.ShiftPhase(self.phi3, self.d3) + sched += pulse.ShiftPhase(self.phi3, self.d3.frame) long_schedule = pulse.ScheduleBlock( alignment_context=AlignEquispaced(self.context_dur), name="long_schedule" ) long_schedule += subroutine - long_schedule += pulse.ShiftPhase(self.phi2, self.d2) + long_schedule += pulse.ShiftPhase(self.phi2, self.d2.frame) long_schedule += pulse.Play(self.parametric_waveform2, self.d2) long_schedule += pulse.Call(sched) long_schedule += pulse.Play(self.parametric_waveform3, self.d3) @@ -124,7 +124,7 @@ def test_get_parameter_from_pulse(self): def test_get_parameter_from_inst(self): """Test get parameters from instruction.""" - test_obj = pulse.ShiftPhase(self.phi1 + self.phi2, pulse.DriveChannel(0)) + test_obj = pulse.ShiftPhase(self.phi1 + self.phi2, pulse.DriveChannel(0).frame) visitor = ParameterGetter() visitor.visit(test_obj) @@ -136,7 +136,7 @@ def test_get_parameter_from_inst(self): def test_get_parameter_from_call(self): """Test get parameters from instruction.""" sched = pulse.Schedule() - sched += pulse.ShiftPhase(self.phi1, self.d1) + sched += pulse.ShiftPhase(self.phi1, self.d1.frame) test_obj = pulse.Call(subroutine=sched) @@ -199,21 +199,21 @@ def test_set_parameter_to_pulse(self): def test_set_parameter_to_inst(self): """Test get parameters from instruction.""" - test_obj = pulse.ShiftPhase(self.phi1 + self.phi2, pulse.DriveChannel(0)) + test_obj = pulse.ShiftPhase(self.phi1 + self.phi2, pulse.DriveChannel(0).frame) value_dict = {self.phi1: 0.123, self.phi2: 0.456} visitor = ParameterSetter(param_map=value_dict) assigned = visitor.visit(test_obj) - ref_obj = pulse.ShiftPhase(0.579, pulse.DriveChannel(0)) + ref_obj = pulse.ShiftPhase(0.579, pulse.DriveChannel(0).frame) self.assertEqual(assigned, ref_obj) def test_set_parameter_to_call(self): """Test get parameters from instruction.""" sched = pulse.Schedule() - sched += pulse.ShiftPhase(self.phi1, self.d1) + sched += pulse.ShiftPhase(self.phi1, self.d1.frame) test_obj = pulse.Call(subroutine=sched) @@ -223,7 +223,7 @@ def test_set_parameter_to_call(self): assigned = visitor.visit(test_obj) ref_sched = pulse.Schedule() - ref_sched += pulse.ShiftPhase(1.57, pulse.DriveChannel(2)) + ref_sched += pulse.ShiftPhase(1.57, pulse.DriveChannel(2).frame) ref_obj = pulse.Call(subroutine=ref_sched) @@ -305,16 +305,16 @@ def test_set_parameter_to_complex_schedule(self): # create ref schedule subroutine = pulse.ScheduleBlock(alignment_context=AlignLeft()) - subroutine += pulse.ShiftPhase(1.0, pulse.DriveChannel(0)) + subroutine += pulse.ShiftPhase(1.0, pulse.DriveChannel(0).frame) subroutine += pulse.Play(pulse.Gaussian(100, 0.3, 25), pulse.DriveChannel(0)) sched = pulse.Schedule() - sched += pulse.ShiftPhase(3.0, pulse.DriveChannel(4)) + sched += pulse.ShiftPhase(3.0, pulse.DriveChannel(4).frame) ref_obj = pulse.ScheduleBlock(alignment_context=AlignEquispaced(1000), name="long_schedule") ref_obj += subroutine - ref_obj += pulse.ShiftPhase(2.0, pulse.DriveChannel(2)) + ref_obj += pulse.ShiftPhase(2.0, pulse.DriveChannel(2).frame) ref_obj += pulse.Play(pulse.Gaussian(125, 0.3, 25), pulse.DriveChannel(2)) ref_obj += pulse.Call(sched) ref_obj += pulse.Play(pulse.Gaussian(150, 0.4, 25), pulse.DriveChannel(4)) diff --git a/test/python/pulse/test_parameters.py b/test/python/pulse/test_parameters.py index d99bc8f77971..489780d98828 100644 --- a/test/python/pulse/test_parameters.py +++ b/test/python/pulse/test_parameters.py @@ -81,11 +81,11 @@ def test_parameter_attribute_schedule(self): """Test the ``parameter`` attributes.""" schedule = pulse.Schedule() self.assertFalse(schedule.is_parameterized()) - schedule += pulse.SetFrequency(self.alpha, DriveChannel(0)) + schedule += pulse.SetFrequency(self.alpha, DriveChannel(0).frame) self.assertEqual(schedule.parameters, {self.alpha}) - schedule += pulse.ShiftFrequency(self.gamma, DriveChannel(0)) + schedule += pulse.ShiftFrequency(self.gamma, DriveChannel(0).frame) self.assertEqual(schedule.parameters, {self.alpha, self.gamma}) - schedule += pulse.SetPhase(self.phi, DriveChannel(1)) + schedule += pulse.SetPhase(self.phi, DriveChannel(1).frame) self.assertTrue(schedule.is_parameterized()) self.assertEqual(schedule.parameters, {self.alpha, self.gamma, self.phi}) schedule.assign_parameters({self.phi: self.alpha, self.gamma: self.shift}) @@ -98,10 +98,10 @@ def test_parameter_attribute_schedule(self): def test_straight_schedule_bind(self): """Nothing fancy, 1:1 mapping.""" schedule = pulse.Schedule() - schedule += pulse.SetFrequency(self.alpha, DriveChannel(0)) - schedule += pulse.ShiftFrequency(self.gamma, DriveChannel(0)) - schedule += pulse.SetPhase(self.phi, DriveChannel(1)) - schedule += pulse.ShiftPhase(self.theta, DriveChannel(1)) + schedule += pulse.SetFrequency(self.alpha, DriveChannel(0).frame) + schedule += pulse.ShiftFrequency(self.gamma, DriveChannel(0).frame) + schedule += pulse.SetPhase(self.phi, DriveChannel(1).frame) + schedule += pulse.ShiftPhase(self.theta, DriveChannel(1).frame) schedule.assign_parameters( { @@ -122,9 +122,9 @@ def test_straight_schedule_bind(self): def test_multiple_parameters(self): """Expressions of parameters with partial assignment.""" schedule = pulse.Schedule() - schedule += pulse.SetFrequency(self.alpha + self.beta, DriveChannel(0)) - schedule += pulse.ShiftFrequency(self.gamma + self.beta, DriveChannel(0)) - schedule += pulse.SetPhase(self.phi, DriveChannel(1)) + schedule += pulse.SetFrequency(self.alpha + self.beta, DriveChannel(0).frame) + schedule += pulse.ShiftFrequency(self.gamma + self.beta, DriveChannel(0).frame) + schedule += pulse.SetPhase(self.phi, DriveChannel(1).frame) # Partial bind delta = 1e9 @@ -148,8 +148,8 @@ def get_shift(variable): return variable - 1 schedule = pulse.Schedule() - schedule += pulse.SetFrequency(get_frequency(self.alpha), DriveChannel(0)) - schedule += pulse.ShiftFrequency(get_shift(self.gamma), DriveChannel(0)) + schedule += pulse.SetFrequency(get_frequency(self.alpha), DriveChannel(0).frame) + schedule += pulse.ShiftFrequency(get_shift(self.gamma), DriveChannel(0).frame) schedule.assign_parameters({self.alpha: self.freq / 2, self.gamma: self.shift + 1}) @@ -160,7 +160,7 @@ def get_shift(variable): def test_substitution(self): """Test Parameter substitution (vs bind).""" schedule = pulse.Schedule() - schedule += pulse.SetFrequency(self.alpha, DriveChannel(0)) + schedule += pulse.SetFrequency(self.alpha, DriveChannel(0).frame) schedule.assign_parameters({self.alpha: 2 * self.beta}) self.assertEqual(schedule.instructions[0][1].frequency, 2 * self.beta) @@ -170,7 +170,7 @@ def test_substitution(self): def test_substitution_with_existing(self): """Test that substituting one parameter with an existing parameter works.""" schedule = pulse.Schedule() - schedule += pulse.SetFrequency(self.alpha, DriveChannel(self.qubit)) + schedule += pulse.SetFrequency(self.alpha, DriveChannel(self.qubit).frame) schedule.assign_parameters({self.alpha: 1e9 * self.qubit}) self.assertEqual(schedule.instructions[0][1].frequency, 1e9 * self.qubit) @@ -180,7 +180,7 @@ def test_substitution_with_existing(self): def test_channels(self): """Test that channel indices can also be parameterized and assigned.""" schedule = pulse.Schedule() - schedule += pulse.ShiftPhase(self.phase, DriveChannel(2 * self.qubit)) + schedule += pulse.ShiftPhase(self.phase, DriveChannel(2 * self.qubit).frame) schedule.assign_parameters({self.qubit: 4}) self.assertEqual(schedule.instructions[0][1].frame, Frame("d", 8)) diff --git a/test/python/pulse/test_schedule.py b/test/python/pulse/test_schedule.py index 592922e26384..856be6df027d 100644 --- a/test/python/pulse/test_schedule.py +++ b/test/python/pulse/test_schedule.py @@ -122,11 +122,11 @@ def test_can_create_valid_schedule(self): sched = Schedule() sched = sched.append(Play(gp0, self.config.drive(0))) - sched = sched.insert(60, ShiftPhase(-1.57, self.config.drive(0))) + sched = sched.insert(60, ShiftPhase(-1.57, self.config.drive(0).frame)) sched = sched.insert(30, Play(gp1, self.config.drive(0))) sched = sched.insert(60, Play(gp0, self.config.control([0, 1])[0])) sched = sched.insert(80, Snapshot("label", "snap_type")) - sched = sched.insert(90, ShiftPhase(1.57, self.config.drive(0))) + sched = sched.insert(90, ShiftPhase(1.57, self.config.drive(0).frame)) sched = sched.insert( 90, Acquire(10, self.config.acquire(0), MemorySlot(0), RegisterSlot(0)) ) @@ -152,11 +152,11 @@ def test_can_create_valid_schedule_with_syntax_sugar(self): sched = Schedule() sched += Play(gp0, self.config.drive(0)) - sched |= ShiftPhase(-1.57, self.config.drive(0)) << 60 + sched |= ShiftPhase(-1.57, self.config.drive(0).frame) << 60 sched |= Play(gp1, self.config.drive(0)) << 30 sched |= Play(gp0, self.config.control(qubits=[0, 1])[0]) << 60 sched |= Snapshot("label", "snap_type") << 60 - sched |= ShiftPhase(1.57, self.config.drive(0)) << 90 + sched |= ShiftPhase(1.57, self.config.drive(0).frame) << 90 sched |= Acquire(10, self.config.acquire(0), MemorySlot(0)) << 90 sched += sched @@ -314,7 +314,7 @@ def test_name_inherited(self): sched_pulse = Play(gp0, self.config.drive(0)) | sched1 self.assertEqual(sched_pulse.name, "pulse_name") - sched_fc = ShiftPhase(0.1, self.config.drive(0), name="fc_name") | sched1 + sched_fc = ShiftPhase(0.1, self.config.drive(0).frame, name="fc_name") | sched1 self.assertEqual(sched_fc.name, "fc_name") sched_snapshot = snapshot | sched1 @@ -417,9 +417,9 @@ def test_duration(self): reference_sched = Schedule() reference_sched = reference_sched.insert(10, Delay(10, DriveChannel(0))) reference_sched = reference_sched.insert(10, Delay(50, DriveChannel(1))) - reference_sched = reference_sched.insert(10, ShiftPhase(0.1, DriveChannel(0))) + reference_sched = reference_sched.insert(10, ShiftPhase(0.1, DriveChannel(0).frame)) - reference_sched = reference_sched.insert(100, ShiftPhase(0.1, DriveChannel(1))) + reference_sched = reference_sched.insert(100, ShiftPhase(0.1, DriveChannel(1).frame)) self.assertEqual(reference_sched.duration, 100) self.assertEqual(reference_sched.duration, 100) @@ -429,9 +429,9 @@ def test_ch_duration(self): reference_sched = Schedule() reference_sched = reference_sched.insert(10, Delay(10, DriveChannel(0))) reference_sched = reference_sched.insert(10, Delay(50, DriveChannel(1))) - reference_sched = reference_sched.insert(10, ShiftPhase(0.1, DriveChannel(0))) + reference_sched = reference_sched.insert(10, ShiftPhase(0.1, DriveChannel(0).frame)) - reference_sched = reference_sched.insert(100, ShiftPhase(0.1, DriveChannel(1))) + reference_sched = reference_sched.insert(100, ShiftPhase(0.1, DriveChannel(1).frame)) self.assertEqual(reference_sched.ch_duration(DriveChannel(0)), 20) self.assertEqual(reference_sched.ch_duration(DriveChannel(1)), 100) @@ -444,9 +444,9 @@ def test_ch_start_time(self): reference_sched = Schedule() reference_sched = reference_sched.insert(10, Delay(10, DriveChannel(0))) reference_sched = reference_sched.insert(10, Delay(50, DriveChannel(1))) - reference_sched = reference_sched.insert(10, ShiftPhase(0.1, DriveChannel(0))) + reference_sched = reference_sched.insert(10, ShiftPhase(0.1, DriveChannel(0).frame)) - reference_sched = reference_sched.insert(100, ShiftPhase(0.1, DriveChannel(1))) + reference_sched = reference_sched.insert(100, ShiftPhase(0.1, DriveChannel(1).frame)) self.assertEqual(reference_sched.ch_start_time(DriveChannel(0)), 10) self.assertEqual(reference_sched.ch_start_time(DriveChannel(1)), 10) @@ -456,9 +456,9 @@ def test_ch_stop_time(self): reference_sched = Schedule() reference_sched = reference_sched.insert(10, Delay(10, DriveChannel(0))) reference_sched = reference_sched.insert(10, Delay(50, DriveChannel(1))) - reference_sched = reference_sched.insert(10, ShiftPhase(0.1, DriveChannel(0))) + reference_sched = reference_sched.insert(10, ShiftPhase(0.1, DriveChannel(0).frame)) - reference_sched = reference_sched.insert(100, ShiftPhase(0.1, DriveChannel(1))) + reference_sched = reference_sched.insert(100, ShiftPhase(0.1, DriveChannel(1).frame)) self.assertEqual(reference_sched.ch_stop_time(DriveChannel(0)), 20) self.assertEqual(reference_sched.ch_stop_time(DriveChannel(1)), 100) @@ -468,9 +468,9 @@ def test_timeslots(self): reference_sched = Schedule() reference_sched = reference_sched.insert(10, Delay(10, DriveChannel(0))) reference_sched = reference_sched.insert(10, Delay(50, DriveChannel(1))) - reference_sched = reference_sched.insert(10, ShiftPhase(0.1, DriveChannel(0))) + reference_sched = reference_sched.insert(10, ShiftPhase(0.1, DriveChannel(0).frame)) - reference_sched = reference_sched.insert(100, ShiftPhase(0.1, DriveChannel(1))) + reference_sched = reference_sched.insert(100, ShiftPhase(0.1, DriveChannel(1).frame)) self.assertEqual(reference_sched.timeslots[DriveChannel(0)], [(10, 10), (10, 20)]) self.assertEqual(reference_sched.timeslots[DriveChannel(1)], [(10, 60), (100, 100)]) @@ -638,7 +638,7 @@ def test_filter_channels(self): sched = Schedule(name="fake_experiment") sched = sched.insert(0, Play(lp0, self.config.drive(0))) sched = sched.insert(10, Play(lp0, self.config.drive(1))) - sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0))) + sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0).frame)) sched = sched.insert(60, Acquire(5, AcquireChannel(0), MemorySlot(0))) sched = sched.insert(60, Acquire(5, AcquireChannel(1), MemorySlot(1))) sched = sched.insert(90, Play(lp0, self.config.drive(0))) @@ -675,10 +675,10 @@ def test_filter_inst_types(self): sched = Schedule(name="fake_experiment") sched = sched.insert(0, Play(lp0, self.config.drive(0))) sched = sched.insert(10, Play(lp0, self.config.drive(1))) - sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0))) - sched = sched.insert(40, SetFrequency(8.0, self.config.drive(0))) - sched = sched.insert(50, ShiftFrequency(4.0e6, self.config.drive(0))) - sched = sched.insert(55, SetPhase(3.14, self.config.drive(0))) + sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0).frame)) + sched = sched.insert(40, SetFrequency(8.0, self.config.drive(0).frame)) + sched = sched.insert(50, ShiftFrequency(4.0e6, self.config.drive(0).frame)) + sched = sched.insert(55, SetPhase(3.14, self.config.drive(0).frame)) for i in range(2): sched = sched.insert(60, Acquire(5, self.config.acquire(i), MemorySlot(i))) sched = sched.insert(90, Play(lp0, self.config.drive(0))) @@ -737,7 +737,7 @@ def test_filter_intervals(self): sched = Schedule(name="fake_experiment") sched = sched.insert(0, Play(lp0, self.config.drive(0))) sched = sched.insert(10, Play(lp0, self.config.drive(1))) - sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0))) + sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0).frame)) for i in range(2): sched = sched.insert(60, Acquire(5, self.config.acquire(i), MemorySlot(i))) sched = sched.insert(90, Play(lp0, self.config.drive(0))) @@ -789,7 +789,7 @@ def test_filter_multiple(self): sched = Schedule(name="fake_experiment") sched = sched.insert(0, Play(lp0, self.config.drive(0))) sched = sched.insert(10, Play(lp0, self.config.drive(1))) - sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0))) + sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0).frame)) for i in range(2): sched = sched.insert(60, Acquire(5, self.config.acquire(i), MemorySlot(i))) @@ -837,7 +837,7 @@ def test_custom_filters(self): sched = Schedule(name="fake_experiment") sched = sched.insert(0, Play(lp0, self.config.drive(0))) sched = sched.insert(10, Play(lp0, self.config.drive(1))) - sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0))) + sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0).frame)) filtered, excluded = self._filter_and_test_consistency(sched, lambda x: True) for i in filtered.instructions: @@ -866,7 +866,7 @@ def test_empty_filters(self): sched = Schedule(name="fake_experiment") sched = sched.insert(0, Play(lp0, self.config.drive(0))) sched = sched.insert(10, Play(lp0, self.config.drive(1))) - sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0))) + sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0).frame)) for i in range(2): sched = sched.insert(60, Acquire(5, self.config.acquire(i), MemorySlot(i))) sched = sched.insert(90, Play(lp0, self.config.drive(0))) @@ -916,28 +916,29 @@ class TestScheduleEquality(BaseTestSchedule): def test_different_channels(self): """Test equality is False if different channels.""" self.assertNotEqual( - Schedule(ShiftPhase(0, DriveChannel(0))), Schedule(ShiftPhase(0, DriveChannel(1))) + Schedule(ShiftPhase(0, DriveChannel(0).frame)), + Schedule(ShiftPhase(0, DriveChannel(1).frame)), ) def test_same_time_equal(self): """Test equal if instruction at same time.""" self.assertEqual( - Schedule((0, ShiftPhase(0, DriveChannel(1)))), - Schedule((0, ShiftPhase(0, DriveChannel(1)))), + Schedule((0, ShiftPhase(0, DriveChannel(1).frame))), + Schedule((0, ShiftPhase(0, DriveChannel(1).frame))), ) def test_different_time_not_equal(self): """Test that not equal if instruction at different time.""" self.assertNotEqual( - Schedule((0, ShiftPhase(0, DriveChannel(1)))), - Schedule((1, ShiftPhase(0, DriveChannel(1)))), + Schedule((0, ShiftPhase(0, DriveChannel(1).frame))), + Schedule((1, ShiftPhase(0, DriveChannel(1).frame))), ) def test_single_channel_out_of_order(self): """Test that schedule with single channel equal when out of order.""" instructions = [ - (0, ShiftPhase(0, DriveChannel(0))), + (0, ShiftPhase(0, DriveChannel(0).frame)), (15, Play(Waveform(np.ones(10)), DriveChannel(0))), (5, Play(Waveform(np.ones(10)), DriveChannel(0))), ] @@ -947,7 +948,7 @@ def test_single_channel_out_of_order(self): def test_multiple_channels_out_of_order(self): """Test that schedule with multiple channels equal when out of order.""" instructions = [ - (0, ShiftPhase(0, DriveChannel(1))), + (0, ShiftPhase(0, DriveChannel(1).frame)), (1, Acquire(10, AcquireChannel(0), MemorySlot(1))), ] @@ -968,8 +969,8 @@ def test_different_name_equal(self): """Test that names are ignored when checking equality.""" self.assertEqual( - Schedule((0, ShiftPhase(0, DriveChannel(1), name="fc1")), name="s1"), - Schedule((0, ShiftPhase(0, DriveChannel(1), name="fc2")), name="s2"), + Schedule((0, ShiftPhase(0, DriveChannel(1).frame, name="fc1")), name="s1"), + Schedule((0, ShiftPhase(0, DriveChannel(1).frame, name="fc2")), name="s2"), ) diff --git a/test/python/pulse/test_transforms.py b/test/python/pulse/test_transforms.py index 67c6b623ef6d..6adc7af23630 100644 --- a/test/python/pulse/test_transforms.py +++ b/test/python/pulse/test_transforms.py @@ -813,6 +813,10 @@ def __init__(self, *channels): def channels(self): return self.operands + @property + def frames(self): + return tuple() + class TestRemoveDirectives(QiskitTestCase): """Test removing of directives.""" From 378a7687841910bd7088d23efbf1f6b65ea1b62b Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 29 Jun 2021 14:49:43 +0200 Subject: [PATCH 105/111] * Renamed resolve_frames to map_frames. * Added test for requires_mapping. --- qiskit/pulse/transforms/__init__.py | 2 +- qiskit/pulse/transforms/base_transforms.py | 4 +- qiskit/pulse/transforms/frames.py | 43 +++++++++++++--------- test/python/pulse/test_frames.py | 33 ++++++++++++++++- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/qiskit/pulse/transforms/__init__.py b/qiskit/pulse/transforms/__init__.py index f1390ece286b..4bdc21c700ec 100644 --- a/qiskit/pulse/transforms/__init__.py +++ b/qiskit/pulse/transforms/__init__.py @@ -105,4 +105,4 @@ ) from qiskit.pulse.transforms.dag import block_to_dag -from qiskit.pulse.transforms.frames import resolve_frames +from qiskit.pulse.transforms.frames import map_frames diff --git a/qiskit/pulse/transforms/base_transforms.py b/qiskit/pulse/transforms/base_transforms.py index 02b1f43476b8..d837d34a2971 100644 --- a/qiskit/pulse/transforms/base_transforms.py +++ b/qiskit/pulse/transforms/base_transforms.py @@ -19,7 +19,7 @@ from qiskit.pulse.schedule import ScheduleBlock, Schedule from qiskit.pulse.transforms import canonicalization from qiskit.pulse.frame import FramesConfiguration -from qiskit.pulse.transforms.frames import resolve_frames +from qiskit.pulse.transforms.frames import map_frames InstructionSched = Union[Tuple[int, Instruction], Instruction] @@ -57,7 +57,7 @@ def target_qobj_transform( sched = canonicalization.remove_directives(sched) # remove any instructions on frames - sched = resolve_frames(sched, frames_config) + sched = map_frames(sched, frames_config) return sched diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 01ea6ff050b8..a34430f6edb5 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -24,12 +24,34 @@ from qiskit.pulse.instructions import ShiftPhase, ShiftFrequency, Play, SetFrequency, SetPhase -def resolve_frames( +def requires_frame_mapping(schedule: Schedule) -> bool: + """Returns True if there are frame instructions or :class:`Signal`s that need mapping. + + Returns: + True if: + - There are Signals in the Schedule + - A SetFrequency, SetPhase, ShiftFrequency, ShiftPhase has a frame that does not + correspond to any PulseChannel. + """ + for time, inst in schedule.instructions: + if isinstance(inst, Play): + if isinstance(inst.operands[0], Signal): + return True + + if isinstance(inst, (SetFrequency, SetPhase, ShiftFrequency, ShiftPhase)): + if inst.channel is None: + return True + + return False + + +def map_frames( schedule: Union[Schedule, ScheduleBlock], frames_config: FramesConfiguration ) -> Schedule: """ - Parse the schedule and replace instructions on Frames by instructions on the - appropriate channels. + Parse the schedule and replace instructions on Frames that do not have a pulse channel + by instructions on the appropriate channels. Also replace Signals with play Pulse + instructions with the appropriate phase/frequency shifts and sets. Args: schedule: The schedule for which to replace frames with the appropriate @@ -49,20 +71,7 @@ def resolve_frames( if isinstance(schedule, ScheduleBlock): schedule = block_to_schedule(schedule) - # Check that the schedule has any frame instructions that need resolving. - # A schedule needs resolving if: - # - A pulse in a frame different from the channel's frame is played, and - # - A Set/Shift instruction is applied on a frame which does not correspond to a channel frame. - for time, inst in schedule.instructions: - if isinstance(inst, Play): - if isinstance(inst.operands[0], Signal): - break - - if isinstance(inst, (SetFrequency, SetPhase, ShiftFrequency, ShiftPhase)): - frame = inst.frame - if not frames_config[frame].has_physical_channel: - break - else: + if not requires_frame_mapping(schedule): return schedule resolved_frames = {} diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 904f105a3e24..af189ffd64dc 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -18,8 +18,9 @@ from qiskit.circuit import Parameter, QuantumCircuit, Gate import qiskit.pulse as pulse from qiskit.pulse.transforms import target_qobj_transform +from qiskit.pulse.transforms.frames import requires_frame_mapping from qiskit.test import QiskitTestCase -from qiskit.pulse.transforms import resolve_frames, block_to_schedule +from qiskit.pulse.transforms import map_frames, block_to_schedule from qiskit.pulse.transforms.resolved_frame import ResolvedFrame from qiskit.pulse.parameter_manager import ParameterManager from qiskit.pulse.frame import Frame, FramesConfiguration, FrameDefinition @@ -223,7 +224,7 @@ def test_phase_advance(self): self.assertAlmostEqual(frame1_.phase(time) % (2 * np.pi), phase % (2 * np.pi), places=8) # Check that the proper phase instructions are added to the frame resolved schedules. - resolved = resolve_frames(sched, frames_config).instructions + resolved = map_frames(sched, frames_config).instructions params = [ (160, 160, self.freq1, self.freq0, 3), @@ -400,3 +401,31 @@ def test_assmble_frames(self): qobj_resolved = assemble(transform(self.expected_schedule), self.backend) self.assertEqual(qobj.experiments, qobj_resolved.experiments) + + +class TestRequiresFrameMapping(QiskitTestCase): + """Test the condition on whether a Schedule requires mapping.""" + + def test_requires_mapping(self): + """Test a few simple schedules.""" + + # No frame mapping needed. + with pulse.build() as sched: + pulse.play(pulse.Gaussian(160, 0.2, 40), pulse.DriveChannel(1)) + pulse.shift_phase(1.57, Frame("d", 0)) + + self.assertFalse(requires_frame_mapping(block_to_schedule(sched))) + + # Frame(Q0) needs frame mapping + with pulse.build() as sched: + pulse.play(pulse.Gaussian(160, 0.2, 40), pulse.DriveChannel(1)) + pulse.shift_phase(1.57, Frame("Q", 0)) + + self.assertTrue(requires_frame_mapping(block_to_schedule(sched))) + + # The signal needs frame mapping + signal = pulse.Signal(pulse.Gaussian(160, 0.2, 40), Frame("d", 0)) + with pulse.build() as sched: + pulse.play(signal, pulse.DriveChannel(1)) + + self.assertTrue(requires_frame_mapping(block_to_schedule(sched))) From 2b738df0db685f64ca5c7dd99a5cf43fd86a2af7 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 29 Jun 2021 16:22:19 +0200 Subject: [PATCH 106/111] * Added raise for calibrations that require frame mapping. --- qiskit/assembler/assemble_circuits.py | 9 +++++++++ qiskit/pulse/transforms/frames.py | 5 ++++- test/python/pulse/test_frames.py | 6 ++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/qiskit/assembler/assemble_circuits.py b/qiskit/assembler/assemble_circuits.py index f97a5593586f..3469fd9811bf 100644 --- a/qiskit/assembler/assemble_circuits.py +++ b/qiskit/assembler/assemble_circuits.py @@ -35,6 +35,7 @@ QobjHeader, ) from qiskit.tools.parallel import parallel_map +from qiskit.pulse.transforms.frames import requires_frame_mapping PulseLibrary = Dict[str, List[complex]] @@ -192,6 +193,12 @@ def _assemble_pulse_gates( pulse_library = {} for gate, cals in circuit.calibrations.items(): for (qubits, params), schedule in cals.items(): + + # This will require the backend to map frames once timings are known in order to + # compute the phase advances. + if requires_frame_mapping(schedule): + raise QiskitError("Calibrations with frames are not yet supported.") + qobj_instructions, _ = _assemble_schedule( schedule, converters.InstructionToQobjConverter(PulseQobjInstruction), @@ -358,6 +365,8 @@ def assemble_circuits( if m_los: qobj_config_dict["meas_lo_freq"] = [freq / 1e9 for freq in m_los] + qobj_config_dict.pop("frames_config", None) + qobj_config = QasmQobjConfig(**qobj_config_dict) qubit_sizes = [] diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index a34430f6edb5..60516d467a58 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -24,7 +24,7 @@ from qiskit.pulse.instructions import ShiftPhase, ShiftFrequency, Play, SetFrequency, SetPhase -def requires_frame_mapping(schedule: Schedule) -> bool: +def requires_frame_mapping(schedule: Union[Schedule, ScheduleBlock]) -> bool: """Returns True if there are frame instructions or :class:`Signal`s that need mapping. Returns: @@ -33,6 +33,9 @@ def requires_frame_mapping(schedule: Schedule) -> bool: - A SetFrequency, SetPhase, ShiftFrequency, ShiftPhase has a frame that does not correspond to any PulseChannel. """ + if isinstance(schedule, ScheduleBlock): + schedule = block_to_schedule(schedule) + for time, inst in schedule.instructions: if isinstance(inst, Play): if isinstance(inst.operands[0], Signal): diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index af189ffd64dc..dc06ddfed69f 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -25,6 +25,7 @@ from qiskit.pulse.parameter_manager import ParameterManager from qiskit.pulse.frame import Frame, FramesConfiguration, FrameDefinition from qiskit.test.mock import FakeAthens +from qiskit.exceptions import QiskitError class TestFrame(QiskitTestCase): @@ -376,6 +377,11 @@ def test_frames_in_circuit(self): self.assertEqual(sched_resolved, expected_resolved) + try: + assemble(circ, self.backend) + except QiskitError as error: + self.assertEqual(error.message, "Calibrations with frames are not yet supported.") + def test_transforms_frame_resolution(self): """Test that resolving frames multiple times does not change the schedule.""" From 02a9f21843caae712d16d34ee53b6b3b1f59566a Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 29 Jun 2021 16:35:41 +0200 Subject: [PATCH 107/111] * Refactored set_frame_instructions. * Added frames_config conversion to dict. --- qiskit/assembler/assemble_circuits.py | 4 +++- qiskit/pulse/transforms/frames.py | 14 +++++++------- qiskit/pulse/transforms/resolved_frame.py | 8 +++++--- test/python/pulse/test_frames.py | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/qiskit/assembler/assemble_circuits.py b/qiskit/assembler/assemble_circuits.py index 3469fd9811bf..44de5bd53fb2 100644 --- a/qiskit/assembler/assemble_circuits.py +++ b/qiskit/assembler/assemble_circuits.py @@ -365,7 +365,9 @@ def assemble_circuits( if m_los: qobj_config_dict["meas_lo_freq"] = [freq / 1e9 for freq in m_los] - qobj_config_dict.pop("frames_config", None) + # Needed so that the backend can map the frames. + if "frames_config" in qobj_config_dict: + qobj_config_dict["frames_config"] = qobj_config_dict["frames_config"].to_dict() qobj_config = QasmQobjConfig(**qobj_config_dict) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 60516d467a58..7afc163bd6cb 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -98,14 +98,14 @@ def map_frames( chan_frame = channel_trackers[chan].frame if inst.frame not in resolved_frames: - raise PulseError(f"{inst.frame} is not configured and cannot " f"be resolved.") + raise PulseError(f"{inst.frame} is not configured and cannot be resolved.") resolved_frame = resolved_frames[inst.frame] frame_freq = resolved_frame.frequency(time) frame_phase = resolved_frame.phase(time) - # If the frequency and phase of the channel has already been set once in + # If the frequency and phase of the channel have already been set once in # the past we compute shifts. if channel_trackers[chan].is_initialized(): freq_diff = frame_freq - channel_trackers[chan].frequency(time) @@ -117,8 +117,8 @@ def map_frames( if abs(phase_diff) > resolved_frame.tolerance: sched.insert(time, ShiftPhase(phase_diff, chan_frame), inplace=True) - # If the channel's phase and frequency has not been set in the past - # we set it now + # If the channel's phase and frequency have not been set in the past + # we set it now. else: if frame_freq != 0: sched.insert(time, SetFrequency(frame_freq, chan_frame), inplace=True) @@ -132,13 +132,13 @@ def map_frames( play = Play(inst.pulse, chan) sched.insert(time, play, inplace=True) - # Insert phase and frequency commands that are ties to physical channels. + # Insert phase and frequency commands that are tied to physical channels. elif isinstance(inst, (SetFrequency, ShiftFrequency)): - if frames_config[inst.frame].has_physical_channel: + if inst.channel is not None: sched.insert(time, inst, inplace=True) elif isinstance(inst, (SetPhase, ShiftPhase)): - if frames_config[inst.frame].has_physical_channel: + if inst.channel is not None: sched.insert(time, inst, inplace=True) elif isinstance( diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index a5405d9824c6..7f9d2d0798ab 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -164,6 +164,7 @@ def __init__(self, frame: Frame, definition: FrameDefinition, sample_duration: f self._purpose = definition.purpose self._tolerance = definition.tolerance self._has_physical_channel = definition.has_physical_channel + self._frame = frame @property def purpose(self) -> str: @@ -177,7 +178,8 @@ def tolerance(self) -> float: def set_frame_instructions(self, schedule: Schedule): """ - Add all matching frame instructions in this schedule to self. + Add all matching frame instructions in this schedule to self if and only if + self does not correspond to a frame that is the native frame of a channel. Args: schedule: The schedule from which to extract frame operations. @@ -190,8 +192,8 @@ def set_frame_instructions(self, schedule: Schedule): frame_instructions = schedule.filter(instruction_types=frame_instruction_types) for time, inst in frame_instructions.instructions: - if isinstance(inst, frame_instruction_types) and self.identifier == inst.frame.name: - if not self._has_physical_channel: + if isinstance(inst, frame_instruction_types) and self._frame.name == inst.frame.name: + if inst.channel is None: if isinstance(inst, ShiftFrequency): self.set_frequency(time, self.frequency(time) + inst.frequency) elif isinstance(inst, SetFrequency): diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index dc06ddfed69f..5e4d2b877f28 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -394,7 +394,7 @@ def test_transforms_frame_resolution(self): self.assertEqual(resolved1, resolved2) - def test_assmble_frames(self): + def test_assemble_frames(self): """Test that the assembler resolves the frames of schedule experiments.""" # Assemble a schedule with frames. From 7f897fbde25624a0af6aaec173414a81c1b77b46 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 29 Jun 2021 16:37:39 +0200 Subject: [PATCH 108/111] * Black. --- qiskit/pulse/frame.py | 8 +- qiskit/pulse/instructions/frequency.py | 6 +- qiskit/pulse/instructions/phase.py | 6 +- qiskit/pulse/instructions/play.py | 2 +- qiskit/pulse/transforms/resolved_frame.py | 1 + test/python/pulse/test_frames.py | 93 ++++++++++++++--------- test/python/pulse/test_instructions.py | 16 +--- 7 files changed, 71 insertions(+), 61 deletions(-) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 990fefc05b5a..9b25dfbc86d8 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -168,7 +168,7 @@ def add_frame( frame: Frame, frequency: float, has_physical_channel: Optional[bool] = False, - purpose: Optional[str] = None + purpose: Optional[str] = None, ): """Add a frame to the frame configuration. @@ -181,9 +181,9 @@ def add_frame( has_physical_channel: Whether this frame is the native frame of a physical channel. purpose: A human readable string describing the purpose of the frame. """ - self._frames[frame] = FrameDefinition(frequency=frequency, - has_physical_channel=has_physical_channel, - purpose=purpose) + self._frames[frame] = FrameDefinition( + frequency=frequency, has_physical_channel=has_physical_channel, purpose=purpose + ) @property def definitions(self) -> List[FrameDefinition]: diff --git a/qiskit/pulse/instructions/frequency.py b/qiskit/pulse/instructions/frequency.py index 3dea42cdc58b..035294d836b6 100644 --- a/qiskit/pulse/instructions/frequency.py +++ b/qiskit/pulse/instructions/frequency.py @@ -17,7 +17,7 @@ import warnings from qiskit.circuit.parameterexpression import ParameterExpression -from qiskit.pulse.channels import PulseChannel, DriveChannel , MeasureChannel, ControlChannel +from qiskit.pulse.channels import PulseChannel, DriveChannel, MeasureChannel, ControlChannel from qiskit.pulse.frame import Frame from qiskit.pulse.instructions.instruction import Instruction @@ -96,7 +96,7 @@ def channels(self) -> Tuple[PulseChannel]: @property def frames(self) -> Tuple[Frame]: """Return the frames this instructions acts on.""" - return (self.frame, ) + return (self.frame,) @property def duration(self) -> int: @@ -171,7 +171,7 @@ def channels(self) -> Tuple[PulseChannel]: @property def frames(self) -> Tuple[Frame]: """Return the frames this instructions acts on.""" - return (self.frame, ) + return (self.frame,) @property def duration(self) -> int: diff --git a/qiskit/pulse/instructions/phase.py b/qiskit/pulse/instructions/phase.py index fd8db734ad30..90ed02a4686f 100644 --- a/qiskit/pulse/instructions/phase.py +++ b/qiskit/pulse/instructions/phase.py @@ -93,14 +93,14 @@ def frame(self) -> Frame: def channels(self) -> Tuple[PulseChannel]: """Returns the channels that this schedule uses.""" if self.channel is not None: - return (self.channel, ) + return (self.channel,) return tuple() @property def frames(self) -> Tuple[Frame]: """Return the frames this instructions acts on.""" - return (self.frame, ) + return (self.frame,) @property def duration(self) -> int: @@ -184,7 +184,7 @@ def channels(self) -> Tuple[PulseChannel]: @property def frames(self) -> Tuple[Frame]: """Return the frames this instructions acts on.""" - return (self.frame, ) + return (self.frame,) @property def duration(self) -> int: diff --git a/qiskit/pulse/instructions/play.py b/qiskit/pulse/instructions/play.py index f3b84ba08aac..0e4dc215c2f7 100644 --- a/qiskit/pulse/instructions/play.py +++ b/qiskit/pulse/instructions/play.py @@ -101,7 +101,7 @@ def channels(self) -> Tuple[PulseChannel]: @property def frames(self) -> Tuple[Frame]: """Return the frames this instructions acts on.""" - return (self.frame, ) + return (self.frame,) @property def duration(self) -> Union[int, ParameterExpression]: diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index 7f9d2d0798ab..d6b29051fd9e 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -29,6 +29,7 @@ @dataclass class TimeFrequencyPhase: """Class to help keep track of time, frequency and phase.""" + time: float frequency: float phase: float diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py index 5e4d2b877f28..2ac521506df8 100644 --- a/test/python/pulse/test_frames.py +++ b/test/python/pulse/test_frames.py @@ -61,16 +61,13 @@ def test_frame_config(self): """Test that frame configs can be properly created.""" config = { - Frame("Q", 0): { - "frequency": 5.5e9, - "purpose": "Frame of qubit 0" - }, + Frame("Q", 0): {"frequency": 5.5e9, "purpose": "Frame of qubit 0"}, Frame("Q", 1): { "frequency": 5.2e9, }, Frame("Q", 2): { "frequency": 5.2e9, - } + }, } frames_config = FramesConfiguration.from_dict(config) @@ -87,19 +84,22 @@ def test_frame_config(self): self.assertEqual(frames_config[Frame("Q", idx)].sample_duration, 0.1e-9) self.assertEqual(frames_config[Frame("Q", idx)].phase, 0.0) - def test_merge_two_configs(self): """Test to see if we can merge two configs.""" - config1 = FramesConfiguration.from_dict({ - Frame("Q", 0): {"frequency": 5.5e9}, - Frame("Q", 1): {"frequency": 5.2e9}, - }) + config1 = FramesConfiguration.from_dict( + { + Frame("Q", 0): {"frequency": 5.5e9}, + Frame("Q", 1): {"frequency": 5.2e9}, + } + ) - config2 = FramesConfiguration.from_dict({ - Frame("Q", 1): {"frequency": 4.5e9}, - Frame("Q", 2): {"frequency": 4.2e9}, - }) + config2 = FramesConfiguration.from_dict( + { + Frame("Q", 1): {"frequency": 4.5e9}, + Frame("Q", 2): {"frequency": 4.2e9}, + } + ) for frame, frame_def in config2.items(): config1[frame] = frame_def @@ -166,7 +166,7 @@ def test_set_phase_frequency(self): r_frame.set_frequency_phase(6, 3.0, 1.0) # Frequency and phases at the time they are set. - expected = [(0, 1, 0), (2, 2, .5), (4, 3, .5), (6, 3, 1)] + expected = [(0, 1, 0), (2, 2, 0.5), (4, 3, 0.5), (6, 3, 1)] for time, freq, phase in expected: self.assertEqual(r_frame.frequency(time), freq) self.assertEqual(r_frame.phase(time), phase) @@ -175,7 +175,7 @@ def test_set_phase_frequency(self): # As we have one sample in between times when phases are set, the total # phase is the phase at the setting time pulse the phase advance during # one sample. - expected = [(1, 1, 0), (3, 2, .5), (5, 3, .5), (7, 3, 1)] + expected = [(1, 1, 0), (3, 2, 0.5), (5, 3, 0.5), (7, 3, 1)] time_step = 1 for time, freq, phase in expected: @@ -201,16 +201,18 @@ def test_phase_advance(self): pulse.play(sig0, d0) pulse.play(sig1, d0) - frames_config = FramesConfiguration.from_dict({ - pulse.Frame("Q", 0): { - "frequency": self.freq0, - "purpose": "Frame of qubit 0.", - }, - pulse.Frame("Q", 1): { - "frequency": self.freq1, - "purpose": "Frame of qubit 1.", - }, - }) + frames_config = FramesConfiguration.from_dict( + { + pulse.Frame("Q", 0): { + "frequency": self.freq0, + "purpose": "Frame of qubit 0.", + }, + pulse.Frame("Q", 1): { + "frequency": self.freq1, + "purpose": "Frame of qubit 1.", + }, + } + ) frames_config.sample_duration = self.dt_ frame0_ = ResolvedFrame(pulse.Frame("Q", 0), FrameDefinition(self.freq0), self.dt_) @@ -261,7 +263,7 @@ def test_phase_advance_with_instructions(self): # Test the phase right before the shift phase instruction phase = np.angle(np.exp(2.0j * np.pi * self.freq0 * 159 * self.dt_)) - self.assertAlmostEqual(frame.phase(159) % (2*np.pi), phase % (2*np.pi), places=8) + self.assertAlmostEqual(frame.phase(159) % (2 * np.pi), phase % (2 * np.pi), places=8) # Test the phase at and after the shift phase instruction phase = np.angle(np.exp(2.0j * np.pi * self.freq0 * 160 * self.dt_)) + 1.0 @@ -305,10 +307,12 @@ def test_implicit_frame(self): pulse.play(pulse.Signal(xp12, frame12), d_chan) pulse.play(xp01, d_chan) - frames_config = FramesConfiguration.from_dict({ - frame01: {"frequency": 5.5e9, "purpose": "Frame of 0 <-> 1."}, - frame12: {"frequency": 5.2e9, "purpose": "Frame of 1 <-> 2."}, - }) + frames_config = FramesConfiguration.from_dict( + { + frame01: {"frequency": 5.5e9, "purpose": "Frame of 0 <-> 1."}, + frame12: {"frequency": 5.2e9, "purpose": "Frame of 1 <-> 2."}, + } + ) frames_config.sample_duration = 0.222e-9 transform = lambda sched: target_qobj_transform(sched, frames_config=frames_config) @@ -335,17 +339,28 @@ def setUp(self): frames_config.add_frame(Frame("t", self.qubit), freq12, purpose="Frame of qutrit.") frames_config.sample_duration = self.backend.configuration().dt - self.xp01 = self.defaults.instruction_schedule_map.get( - 'x', qubits=self.qubit - ).instructions[0][1].pulse + self.xp01 = ( + self.defaults.instruction_schedule_map.get("x", qubits=self.qubit) + .instructions[0][1] + .pulse + ) self.xp12 = pulse.Gaussian(160, 0.1, 40) # Create the expected schedule with all the explicit frames with pulse.build(backend=self.backend, default_alignment="sequential") as expected_schedule: - pulse.play(pulse.Signal(self.xp01, pulse.Frame("d", self.qubit)), pulse.drive_channel(self.qubit)) - pulse.play(pulse.Signal(self.xp12, pulse.Frame("t", self.qubit)), pulse.drive_channel(self.qubit)) - pulse.play(pulse.Signal(self.xp01, pulse.Frame("d", self.qubit)), pulse.drive_channel(self.qubit)) + pulse.play( + pulse.Signal(self.xp01, pulse.Frame("d", self.qubit)), + pulse.drive_channel(self.qubit), + ) + pulse.play( + pulse.Signal(self.xp12, pulse.Frame("t", self.qubit)), + pulse.drive_channel(self.qubit), + ) + pulse.play( + pulse.Signal(self.xp01, pulse.Frame("d", self.qubit)), + pulse.drive_channel(self.qubit), + ) pulse.measure(qubits=[self.qubit]) self.expected_schedule = expected_schedule @@ -357,7 +372,9 @@ def test_frames_in_circuit(self): """ with pulse.build(backend=self.backend, default_alignment="sequential") as xp12_schedule: - pulse.play(pulse.Signal(self.xp12, Frame("t", self.qubit)), pulse.drive_channel(self.qubit)) + pulse.play( + pulse.Signal(self.xp12, Frame("t", self.qubit)), pulse.drive_channel(self.qubit) + ) # Create a quantum circuit and attach the pulse to it. circ = QuantumCircuit(1) diff --git a/test/python/pulse/test_instructions.py b/test/python/pulse/test_instructions.py index 71f102acd2c9..9f0e8afc5b81 100644 --- a/test/python/pulse/test_instructions.py +++ b/test/python/pulse/test_instructions.py @@ -140,12 +140,8 @@ def test_freq(self): self.assertEqual(set_freq.duration, 0) self.assertEqual(set_freq.frequency, 4.5e9) self.assertEqual(set_freq.operands, (4.5e9, channels.DriveChannel(1).frame)) - self.assertEqual( - set_freq, instructions.SetFrequency(4.5e9, Frame("d", 1), name="test") - ) - self.assertNotEqual( - set_freq, instructions.SetFrequency(4.5e8, Frame("d", 1), name="test") - ) + self.assertEqual(set_freq, instructions.SetFrequency(4.5e9, Frame("d", 1), name="test")) + self.assertNotEqual(set_freq, instructions.SetFrequency(4.5e8, Frame("d", 1), name="test")) self.assertEqual(repr(set_freq), "SetFrequency(4500000000.0, Frame(d1), name='test')") def test_frame(self): @@ -175,12 +171,8 @@ def test_default(self): self.assertEqual(shift_phase.duration, 0) self.assertEqual(shift_phase.phase, 1.57) self.assertEqual(shift_phase.operands, (1.57, channels.DriveChannel(0).frame)) - self.assertEqual( - shift_phase, instructions.ShiftPhase(1.57, Frame("d", 0), name="test") - ) - self.assertNotEqual( - shift_phase, instructions.ShiftPhase(1.57j, Frame("d", 0), name="test") - ) + self.assertEqual(shift_phase, instructions.ShiftPhase(1.57, Frame("d", 0), name="test")) + self.assertNotEqual(shift_phase, instructions.ShiftPhase(1.57j, Frame("d", 0), name="test")) self.assertEqual(repr(shift_phase), "ShiftPhase(1.57, Frame(d0))") def test_frame(self): From 7650f8127230732a38d72e1dce2ee4920cbacb07 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 2 Jul 2021 19:11:28 +0200 Subject: [PATCH 109/111] * Refactor of frames. --- qiskit/pulse/frame.py | 3 +++ qiskit/pulse/transforms/frames.py | 18 +++--------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/qiskit/pulse/frame.py b/qiskit/pulse/frame.py index 9b25dfbc86d8..43a5f869758d 100644 --- a/qiskit/pulse/frame.py +++ b/qiskit/pulse/frame.py @@ -204,6 +204,9 @@ def items(self): """Return the items in the frames config.""" return self._frames.items() + def get(self, frame: Frame, default: FrameDefinition) -> FrameDefinition: + return self._frames.get(frame, default) + def __getitem__(self, frame: Frame) -> FrameDefinition: """Return the frame definition.""" return self._frames[frame] diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 7afc163bd6cb..e77e4e9018b9 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -13,6 +13,7 @@ """Replace a schedule with frames by one with instructions on PulseChannels only.""" from typing import Union +import numpy as np from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.pulse.transforms.resolved_frame import ResolvedFrame, ChannelTracker @@ -109,7 +110,7 @@ def map_frames( # the past we compute shifts. if channel_trackers[chan].is_initialized(): freq_diff = frame_freq - channel_trackers[chan].frequency(time) - phase_diff = frame_phase - channel_trackers[chan].phase(time) + phase_diff = (frame_phase - channel_trackers[chan].phase(time)) % (2 * np.pi) if abs(freq_diff) > resolved_frame.tolerance: sched.insert(time, ShiftFrequency(freq_diff, chan_frame), inplace=True) @@ -117,15 +118,6 @@ def map_frames( if abs(phase_diff) > resolved_frame.tolerance: sched.insert(time, ShiftPhase(phase_diff, chan_frame), inplace=True) - # If the channel's phase and frequency have not been set in the past - # we set it now. - else: - if frame_freq != 0: - sched.insert(time, SetFrequency(frame_freq, chan_frame), inplace=True) - - if frame_phase != 0: - sched.insert(time, SetPhase(frame_phase, chan_frame), inplace=True) - # Update the frequency and phase of this channel. channel_trackers[chan].set_frequency_phase(time, frame_freq, frame_phase) @@ -133,11 +125,7 @@ def map_frames( sched.insert(time, play, inplace=True) # Insert phase and frequency commands that are tied to physical channels. - elif isinstance(inst, (SetFrequency, ShiftFrequency)): - if inst.channel is not None: - sched.insert(time, inst, inplace=True) - - elif isinstance(inst, (SetPhase, ShiftPhase)): + elif isinstance(inst, (SetFrequency, ShiftFrequency, SetPhase, ShiftPhase)): if inst.channel is not None: sched.insert(time, inst, inplace=True) From 71b51d3a57c570c8959011c9adaa6a1cd48e0726 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 2 Jul 2021 19:27:43 +0200 Subject: [PATCH 110/111] * Simplified channel tracking by removing the initialization. --- qiskit/pulse/transforms/frames.py | 17 +++++++++-------- qiskit/pulse/transforms/resolved_frame.py | 13 +++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index e77e4e9018b9..0143d7736c05 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -89,7 +89,9 @@ def map_frames( channel_trackers = {} for ch in schedule.channels: if isinstance(ch, chans.PulseChannel): - channel_trackers[ch] = ChannelTracker(ch, frames_config.sample_duration) + channel_trackers[ch] = ChannelTracker( + ch, frames_config[ch.frame].frequency, frames_config.sample_duration + ) sched = Schedule(name=schedule.name, metadata=schedule.metadata) @@ -108,15 +110,14 @@ def map_frames( # If the frequency and phase of the channel have already been set once in # the past we compute shifts. - if channel_trackers[chan].is_initialized(): - freq_diff = frame_freq - channel_trackers[chan].frequency(time) - phase_diff = (frame_phase - channel_trackers[chan].phase(time)) % (2 * np.pi) + freq_diff = frame_freq - channel_trackers[chan].frequency(time) + phase_diff = (frame_phase - channel_trackers[chan].phase(time)) % (2 * np.pi) - if abs(freq_diff) > resolved_frame.tolerance: - sched.insert(time, ShiftFrequency(freq_diff, chan_frame), inplace=True) + if abs(freq_diff) > resolved_frame.tolerance: + sched.insert(time, ShiftFrequency(freq_diff, chan_frame), inplace=True) - if abs(phase_diff) > resolved_frame.tolerance: - sched.insert(time, ShiftPhase(phase_diff, chan_frame), inplace=True) + if abs(phase_diff) > resolved_frame.tolerance: + sched.insert(time, ShiftPhase(phase_diff, chan_frame), inplace=True) # Update the frequency and phase of this channel. channel_trackers[chan].set_frequency_phase(time, frame_freq, frame_phase) diff --git a/qiskit/pulse/transforms/resolved_frame.py b/qiskit/pulse/transforms/resolved_frame.py index d6b29051fd9e..09fdd7cb59a0 100644 --- a/qiskit/pulse/transforms/resolved_frame.py +++ b/qiskit/pulse/transforms/resolved_frame.py @@ -213,21 +213,18 @@ def __repr__(self): class ChannelTracker(Tracker): """Class to track the phase and frequency of channels when resolving frames.""" - def __init__(self, channel: PulseChannel, sample_duration: float): + def __init__(self, channel: PulseChannel, frequency: float, sample_duration: float): """ Args: channel: The channel that this tracker tracks. - sample_duration: Duration of a sample. + frequency: The starting frequency of the channel tracker. + sample_duration: The sample duration on the backend. """ super().__init__(channel.name, sample_duration) self._channel = channel - self._frame = Frame(channel.prefix, channel.index) + self._frequencies_phases = [TimeFrequencyPhase(time=0, frequency=frequency, phase=0.0)] @property def frame(self) -> Frame: """Return the native frame of this channel.""" - return self._frame - - def is_initialized(self) -> bool: - """Return true if the channel has been initialized.""" - return len(self._frequencies_phases) > 0 + return self._channel.frame From 7ce6add01d5fc8ef68844f49ddae37abc208a49b Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sat, 3 Jul 2021 13:33:46 +0200 Subject: [PATCH 111/111] * Made mapping cleaner. --- qiskit/pulse/transforms/frames.py | 41 ++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/qiskit/pulse/transforms/frames.py b/qiskit/pulse/transforms/frames.py index 0143d7736c05..31aa0aa994ab 100644 --- a/qiskit/pulse/transforms/frames.py +++ b/qiskit/pulse/transforms/frames.py @@ -78,12 +78,12 @@ def map_frames( if not requires_frame_mapping(schedule): return schedule - resolved_frames = {} + frame_trackers = {} for frame, frame_def in frames_config.items(): - resolved_frames[frame] = ResolvedFrame(frame, frame_def, frames_config.sample_duration) + frame_trackers[frame] = ResolvedFrame(frame, frame_def, frames_config.sample_duration) # Extract shift and set frame operations from the schedule. - resolved_frames[frame].set_frame_instructions(schedule) + frame_trackers[frame].set_frame_instructions(schedule) # Used to keep track of the frequency and phase of the channels channel_trackers = {} @@ -97,26 +97,39 @@ def map_frames( for time, inst in schedule.instructions: if isinstance(inst, Play): + + if inst.frame not in frame_trackers: + raise PulseError(f"{inst.frame} is not configured and cannot be resolved.") + + # The channel on which the pulse or signal is played. chan = inst.channel + + # The frame of the instruction: for a pulse this is simply the channel frame. + inst_frame = inst.frame + + # The frame to which the instruction will be mapped. chan_frame = channel_trackers[chan].frame - if inst.frame not in resolved_frames: - raise PulseError(f"{inst.frame} is not configured and cannot be resolved.") + # Get trackers + frame_tracker = frame_trackers[inst_frame] + chan_tracker = channel_trackers[chan] - resolved_frame = resolved_frames[inst.frame] + # Get the current frequency and phase of the frame. + frame_freq = frame_tracker.frequency(time) + frame_phase = frame_tracker.phase(time) - frame_freq = resolved_frame.frequency(time) - frame_phase = resolved_frame.phase(time) + # Get the current frequency and phase of the channel. + chan_freq = chan_tracker.frequency(time) + chan_phase = chan_tracker.phase(time) - # If the frequency and phase of the channel have already been set once in - # the past we compute shifts. - freq_diff = frame_freq - channel_trackers[chan].frequency(time) - phase_diff = (frame_phase - channel_trackers[chan].phase(time)) % (2 * np.pi) + # Compute the differences + freq_diff = frame_freq - chan_freq + phase_diff = (frame_phase - chan_phase + np.pi) % (2 * np.pi) - np.pi - if abs(freq_diff) > resolved_frame.tolerance: + if abs(freq_diff) > frame_tracker.tolerance: sched.insert(time, ShiftFrequency(freq_diff, chan_frame), inplace=True) - if abs(phase_diff) > resolved_frame.tolerance: + if abs(phase_diff) > frame_tracker.tolerance: sched.insert(time, ShiftPhase(phase_diff, chan_frame), inplace=True) # Update the frequency and phase of this channel.