From 7cd139649d913ed9d492444cc2af845fbef9edfa Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Tue, 20 Feb 2024 06:46:41 +0200 Subject: [PATCH 01/15] Basic implementation --- qiskit/pulse/compiler/__init__.py | 16 + qiskit/pulse/compiler/temp_passes.py | 146 +++++ qiskit/pulse/instructions/__init__.py | 2 +- qiskit/pulse/ir/__init__.py | 3 +- qiskit/pulse/ir/alignments.py | 33 ++ qiskit/pulse/ir/ir.py | 312 +++-------- .../pulse/test_pulse_compiler_passes.py | 514 ++++++++++++++++++ test/python/pulse/test_pulse_ir.py | 417 ++++---------- 8 files changed, 878 insertions(+), 565 deletions(-) create mode 100644 qiskit/pulse/compiler/__init__.py create mode 100644 qiskit/pulse/compiler/temp_passes.py create mode 100644 qiskit/pulse/ir/alignments.py create mode 100644 test/python/pulse/test_pulse_compiler_passes.py diff --git a/qiskit/pulse/compiler/__init__.py b/qiskit/pulse/compiler/__init__.py new file mode 100644 index 000000000000..eaecc16c0312 --- /dev/null +++ b/qiskit/pulse/compiler/__init__.py @@ -0,0 +1,16 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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. + +""" +Temporary Compiler folder +""" +from .temp_passes import analyze_target_frame_pass, sequence_pass, schedule_pass diff --git a/qiskit/pulse/compiler/temp_passes.py b/qiskit/pulse/compiler/temp_passes.py new file mode 100644 index 000000000000..72f9227e45a2 --- /dev/null +++ b/qiskit/pulse/compiler/temp_passes.py @@ -0,0 +1,146 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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. + +""" +Temporary Compiler passes +""" + +from collections import defaultdict +from functools import singledispatch +from rustworkx import PyDAG + +from qiskit.pulse.model import MixedFrame +from qiskit.pulse.ir import IrBlock +from qiskit.pulse.ir.alignments import Alignment, AlignLeft + + +def analyze_target_frame_pass(ir_block: IrBlock, property_set) -> None: + """Map the dependency of ``MixedFrame``s on ``PulseTarget`` and ``Frame``. + + Recursively traverses through the ``ir_block`` to find all ``MixedFrame``s, + and maps their dependencies on ``PulseTarget`` and ``Frame``. The results + are added as a dictionary to ``property_set`` under the key ``target_frame_map``. + The added dictionary is keyed on every ``PulseTarget`` and ``Frame`` in ``ir_block`` + with the value being a set of all ``MixedFrame``s associated with the key. + """ + target_frame_map = defaultdict(set) + _analyze_target_frame_in_block(ir_block, target_frame_map) + property_set["target_frame_map"] = dict(target_frame_map) + + +def _analyze_target_frame_in_block(ir_block: IrBlock, target_frame_map: dict) -> None: + """A helper function to recurse through the block while mapping mixed frame dependency""" + for elm in ir_block.elements(): + # Sub Block + if isinstance(elm, IrBlock): + _analyze_target_frame_in_block(elm, target_frame_map) + # Pulse Instruction + else: + if isinstance(elm.inst_target, MixedFrame): + target_frame_map[elm.inst_target.frame].add(elm.inst_target) + target_frame_map[elm.inst_target.pulse_target].add(elm.inst_target) + + +def sequence_pass(ir_block: IrBlock, property_set: dict) -> IrBlock: + """Finalize the sequence of the IrBlock by adding edges to the DAG""" + _sequence_instructions(ir_block.alignment, ir_block._sequence, property_set) + return ir_block + + +@singledispatch +def _sequence_instructions(alignment: Alignment, sequence: PyDAG, property_set: dict): + """Finalize the sequence of the IrBlock by adding edges to the DAG""" + raise NotImplementedError + + +@_sequence_instructions.register(AlignLeft) +def _sequence_left_justfied(alignment, sequence, property_set): + """Finalize the sequence of the IrBlock by recursively adding edges to the DAG, + aligning elements to the left.""" + idle_after = {} + for ni in sequence.node_indices(): + if ni in (0, 1): + # In, Out node + continue + node = sequence.get_node_data(ni) + node_mixed_frames = set() + + if isinstance(node, IrBlock): + # Recurse over sub block + sequence_pass(node, property_set) + inst_targets = node.inst_targets + else: + inst_targets = [node.inst_target] + + for inst_target in inst_targets: + if isinstance(inst_target, MixedFrame): + node_mixed_frames.add(inst_target) + else: + node_mixed_frames |= property_set["target_frame_map"][inst_target] + + pred_nodes = [ + idle_after[mixed_frame] + for mixed_frame in node_mixed_frames + if mixed_frame in idle_after + ] + if len(pred_nodes) == 0: + pred_nodes = [0] + for pred_node in pred_nodes: + sequence.add_edge(pred_node, ni, None) + for mixed_frame in node_mixed_frames: + idle_after[mixed_frame] = ni + sequence.add_edges_from_no_data([(ni, 1) for ni in idle_after.values()]) + + +def schedule_pass(ir_block: IrBlock, property_set: dict) -> IrBlock: + """Recursively schedule the block by setting initial time for every element""" + # mutate graph + _schedule_elements(ir_block.alignment, ir_block._time_table, ir_block._sequence, property_set) + return ir_block + + +@singledispatch +def _schedule_elements(alignment, table, sequence, property_set): + """Recursively schedule the block by setting initial time for every element""" + raise NotImplementedError + + +@_schedule_elements.register(AlignLeft) +def _schedule_left_justfied(alignment, table, sequence: PyDAG, property_set): + """Recursively schedule the block by setting initial time for every element, + aligning elements to the left.""" + + first_nodes = sequence.successor_indices(0) + nodes = [] + # Node 0 has no duration so is treated separately + for node_index in first_nodes: + table[node_index] = 0 + node = sequence.get_node_data(node_index) + if isinstance(node, IrBlock): + # Recruse over sub blocks + schedule_pass(node, property_set) + nodes.extend(sequence.successor_indices(node_index)) + + while nodes: + node_index = nodes.pop(0) + if node_index == 1 or node_index in table: + # reached end or already scheduled + continue + node = sequence.get_node_data(node_index) + if isinstance(node, IrBlock): + # Recruse over sub blocks + schedule_pass(node, property_set) + # Because we go in order, all predecessors are already scheduled + preds = sequence.predecessor_indices(node_index) + t0 = max([table.get(pred) + sequence.get_node_data(pred).duration for pred in preds]) + table[node_index] = t0 + nodes.extend(sequence.successor_indices(node_index)) diff --git a/qiskit/pulse/instructions/__init__.py b/qiskit/pulse/instructions/__init__.py index e97ac27fc62f..00b7d9634c69 100644 --- a/qiskit/pulse/instructions/__init__.py +++ b/qiskit/pulse/instructions/__init__.py @@ -59,7 +59,7 @@ from .acquire import Acquire from .delay import Delay from .directives import Directive, RelativeBarrier, TimeBlockade -from .instruction import Instruction +from .instruction import Instruction, FrameUpdate from .frequency import SetFrequency, ShiftFrequency from .phase import ShiftPhase, SetPhase from .play import Play diff --git a/qiskit/pulse/ir/__init__.py b/qiskit/pulse/ir/__init__.py index 9abc33e861c5..f34ef43dc0ef 100644 --- a/qiskit/pulse/ir/__init__.py +++ b/qiskit/pulse/ir/__init__.py @@ -18,5 +18,4 @@ ======================================= """ - -from .ir import IrElement, IrBlock, IrInstruction +from .ir import IrBlock diff --git a/qiskit/pulse/ir/alignments.py b/qiskit/pulse/ir/alignments.py new file mode 100644 index 000000000000..9e48d4b21f6b --- /dev/null +++ b/qiskit/pulse/ir/alignments.py @@ -0,0 +1,33 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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. + + +class Alignment: + @property + def conditions(self) -> dict: + return {} + + +class ParallelAlignment(Alignment): + pass + + +class SequentialAlignment(Alignment): + pass + + +class AlignLeft(ParallelAlignment): + pass + + +class AlignSequential(SequentialAlignment): + pass diff --git a/qiskit/pulse/ir/ir.py b/qiskit/pulse/ir/ir.py index 8b3a57bc8ee7..f02fed5c03aa 100644 --- a/qiskit/pulse/ir/ir.py +++ b/qiskit/pulse/ir/ir.py @@ -20,279 +20,107 @@ """ from __future__ import annotations -from typing import List -from abc import ABC, abstractmethod -import numpy as np +import rustworkx as rx +from rustworkx import PyDAG -from qiskit.pulse.exceptions import PulseError +from qiskit.pulse.ir.alignments import Alignment +from qiskit.pulse import Instruction -from qiskit.pulse.transforms import AlignmentKind -from qiskit.pulse.instructions import Instruction +InNode = object() +OutNode = object() -class IrElement(ABC): - """Base class for Pulse IR elements""" - - @property - @abstractmethod - def initial_time(self) -> int | None: - """Return the initial time of the element""" - pass - - @property - @abstractmethod - def duration(self) -> int | None: - """Return the duration of the element""" - pass - - @abstractmethod - def shift_initial_time(self, value: int): - """Shift ``initial_time`` - - Shifts ``initial_time`` to ``initial_time+value``. - - Args: - value: The integer value by which ``initial_time`` is to be shifted. - """ - pass - - @property - def final_time(self) -> int | None: - """Return the final time of the element""" - try: - return self.initial_time + self.duration - except TypeError: - return None - - -class IrInstruction(IrElement): - """Pulse IR Instruction +class IrBlock: + """IR representation of instruction sequences - A Pulse IR instruction represents a ``ScheduleBlock`` instruction, with the addition of - an ``initial_time`` property. + ``IrBlock`` is the backbone of the intermediate representation used in the Qiskit Pulse compiler. + A pulse program is represented as a single ``IrBlock`` object, with elements + which include ``IrInstruction`` objects and other nested ``IrBlock`` objects. """ - def __init__(self, instruction: Instruction, initial_time: int | None = None): - """Pulse IR instructions + def __init__(self, alignment: Alignment): + self._alignment = alignment - Args: - instruction: the Pulse `Instruction` represented by this IR instruction. - initial_time (Optional): Starting time of the instruction. Defaults to ``None`` - """ - self._instruction = instruction - if initial_time is None: - self._initial_time = None - else: - self.initial_time = initial_time + self.time_offset = 0 + self._time_table = {} + self._children = [] + self._sequence = rx.PyDAG(multigraph=False) + self._sequence.add_nodes_from([InNode, OutNode]) @property - def instruction(self) -> Instruction: - """Return the instruction associated with the IrInstruction""" - return self._instruction + def alignment(self) -> Alignment: + """Return the alignment of the IrBlock""" + return self._alignment @property - def initial_time(self) -> int | None: - """A clock time (in terms of system ``dt``) when this instruction is issued. - - .. note:: - initial_time value defaults to ``None`` and can only be set to non-negative integer. - """ - return self._initial_time + def inst_targets(self) -> set: + """Recursively return a set of all Instruction.inst_target in the IrBlock""" + inst_targets = set() + for elm in self.elements(): + if isinstance(elm, IrBlock): + inst_targets |= elm.inst_targets + else: + inst_targets.add(elm.inst_target) + return inst_targets @property - def duration(self) -> int: - """The duration of the instruction (in terms of system ``dt``).""" - return self._instruction.duration + def sequence(self) -> PyDAG: + """Return the DAG sequence of the IrBlock""" + return self._sequence - @initial_time.setter - def initial_time(self, value: int): - """Set ``initial_time`` + def append(self, element: IrBlock | Instruction) -> None: + """Append element to the IrBlock""" + new_node_id = self._sequence.add_node(element) + if isinstance(element, IrBlock): + self._children.append(new_node_id) - Args: - value: The integer value of ``initial_time``. - - Raises: - PulseError: if ``value`` is not ``None`` and not non-negative integer. - """ - if not isinstance(value, (int, np.integer)) or value < 0: - raise PulseError("initial_time must be a non-negative integer") - self._initial_time = value + def elements(self) -> list[IrBlock | Instruction]: + """Return a list of all elements in the IrBlock""" + return self._sequence.nodes()[2:] - def shift_initial_time(self, value: int) -> None: - """Shift ``initial_time`` + def scheduled_elements(self) -> list[list[int | None, IrBlock | Instruction]]: + """Return a list of scheduled elements. - Shifts ``initial_time`` to ``initial_time+value``. - - Args: - value: The integer value by which ``initial_time`` is to be shifted. - - Raises: - PulseError: If the instruction is not scheduled. + Each element in the list is [initial_time, element]. """ - if self.initial_time is None: - raise PulseError("Can not shift initial_time of an untimed element") - - # validation of new initial_time is done in initial_time setter. - self.initial_time = self.initial_time + value - - def __eq__(self, other: "IrInstruction") -> bool: - """Return True iff self and other are equal - - Args: - other: The IR instruction to compare to this one. - - Returns: - True iff equal. - """ - return ( - type(self) is type(other) - and self._instruction == other.instruction - and self._initial_time == other.initial_time - ) - - def __repr__(self) -> str: - """IrInstruction representation""" - return f"IrInstruction({self.instruction}, t0={self.initial_time})" - + return [ + [self._time_table.get(ni, None), self._sequence.get_node_data(ni)] + for ni in self._sequence.node_indices() + if ni not in (0, 1) + ] -class IrBlock(IrElement): - """IR representation of instruction sequences - - ``IrBlock`` is the backbone of the intermediate representation used in the Qiskit Pulse compiler. - A pulse program is represented as a single ``IrBlock`` object, with elements - which include ``IrInstruction`` objects and other nested ``IrBlock`` objects. - """ - - def __init__(self, alignment: AlignmentKind): - """Create ``IrBlock`` object - - Args: - alignment: The ``AlignmentKind`` to be used for scheduling. - """ - self._elements = [] - self._alignment = alignment - - @property def initial_time(self) -> int | None: - """Return the initial time ``initial_time`` of the object""" - elements_initial_times = [element.initial_time for element in self._elements] - if None in elements_initial_times: + """Return initial time""" + first_nodes = self._sequence.successor_indices(0) + if not first_nodes: return None - else: - return min(elements_initial_times, default=None) + return min([self._time_table.get(node, None) for node in first_nodes], default=None) - @property def final_time(self) -> int | None: - """Return the final time of the ``IrBlock``object""" - elements_final_times = [element.final_time for element in self._elements] - if None in elements_final_times: + """Return final time""" + last_nodes = self._sequence.predecessor_indices(1) + if not last_nodes: return None - else: - return max(elements_final_times, default=None) + tf = None + for ni in last_nodes: + if (t0 := self._time_table.get(ni, None)) is not None: + duration = self._sequence.get_node_data(ni).duration + if tf is None: + tf = t0 + duration + else: + tf = max(tf, t0 + duration) + return tf @property def duration(self) -> int | None: - """Return the duration of the ir block""" + """Return the duration of the IrBlock""" try: - return self.final_time - self.initial_time + return self.final_time() - self.initial_time() except TypeError: return None - @property - def elements(self) -> List[IrElement]: - """Return the elements of the ``IrBlock`` object""" - return self._elements - - @property - def alignment(self) -> AlignmentKind: - """Return the alignment of the ``IrBlock`` object""" - return self._alignment - - def has_child_ir(self) -> bool: - """Check if IrBlock has child IrBlock object - - Returns: - ``True`` if object has ``IrBlock`` object in its elements, and ``False`` otherwise. - - """ - for element in self._elements: - if isinstance(element, IrBlock): - return True - return False - - def add_element( - self, - element: IrElement | List[IrElement], - ): - """Adds IR element or list thereof to the ``IrBlock`` object. - - Args: - element: `IrElement` object, or list thereof to add. - """ - if not isinstance(element, list): - element = [element] - - self._elements.extend(element) - - def shift_initial_time(self, value: int): - """Shifts ``initial_time`` of the ``IrBlock`` elements - - The ``initial_time`` of all elements in the ``IrBlock`` are shifted by ``value``, - including recursively if any element is ``IrBlock``. - - Args: - value: The integer value by which ``initial_time`` is to be shifted. - - Raises: - PulseError: if the object is not scheduled (initial_time is None) - """ - if self.initial_time is None: - raise PulseError("Can not shift initial_time of IrBlock with unscheduled elements") - - for element in self.elements: - element.shift_initial_time(value) - - def __eq__(self, other: "IrBlock") -> bool: - """Return True iff self and other are equal - Specifically, iff all of their properties are identical. - - Args: - other: The IrBlock to compare to this one. - - Returns: - True iff equal. - """ - if ( - type(self) is not type(self) - or self._alignment != other._alignment - or len(self._elements) != len(other._elements) - ): + def __eq__(self, other): + if self.alignment != other.alignment: return False - for element_self, element_other in zip(self._elements, other._elements): - if element_other != element_self: - return False - - return True - - def __len__(self) -> int: - """Return the length of the IR, defined as the number of elements in it""" - return len(self._elements) - - def __repr__(self) -> str: - """IrBlock representation""" - inst_count = len( - [element for element in self.elements if isinstance(element, IrInstruction)] - ) - block_count = len(self) - inst_count - reprstr = f"IrBlock({self.alignment}" - if inst_count > 0: - reprstr += f", {inst_count} IrInstructions" - if block_count > 0: - reprstr += f", {block_count} IrBlocks" - if self.initial_time is not None: - reprstr += f", initial_time={self.initial_time}" - if self.duration is not None: - reprstr += f", duration={self.duration}" - reprstr += ")" - return reprstr + return rx.is_isomorphic_node_match(self._sequence, other._sequence, lambda x, y: x == y) diff --git a/test/python/pulse/test_pulse_compiler_passes.py b/test/python/pulse/test_pulse_compiler_passes.py new file mode 100644 index 000000000000..3d1c290f816f --- /dev/null +++ b/test/python/pulse/test_pulse_compiler_passes.py @@ -0,0 +1,514 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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 compiler passes""" + +from test import QiskitTestCase +from qiskit.pulse import ( + Constant, + Play, + Delay, + ShiftPhase, +) + +from qiskit.pulse.ir import ( + IrBlock, +) + +from qiskit.pulse.ir.alignments import AlignLeft +from qiskit.pulse.model import QubitFrame, Qubit, MixedFrame +from qiskit.pulse.compiler import analyze_target_frame_pass, sequence_pass, schedule_pass + + +class TestAnalyzeTargetFramePass(QiskitTestCase): + """Test analyze_target_frame_pass""" + + def test_basic_ir(self): + """test with basic IR""" + ir_example = IrBlock(AlignLeft()) + mf = MixedFrame(Qubit(0), QubitFrame(1)) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf)) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + self.assertEqual(len(property_set.keys()), 1) + mapping = property_set["target_frame_map"] + self.assertEqual(len(mapping), 2) + self.assertEqual(mapping[mf.pulse_target], {mf}) + self.assertEqual(mapping[mf.frame], {mf}) + + mf2 = MixedFrame(Qubit(0), QubitFrame(2)) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf2)) + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + mapping = property_set["target_frame_map"] + self.assertEqual(len(mapping), 3) + self.assertEqual(mapping[mf.pulse_target], {mf, mf2}) + self.assertEqual(mapping[mf.frame], {mf}) + + def test_with_several_inst_target_types(self): + """test with different inst_target types""" + ir_example = IrBlock(AlignLeft()) + mf = MixedFrame(Qubit(0), QubitFrame(1)) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf)) + ir_example.append(Delay(100, target=Qubit(2))) + ir_example.append(ShiftPhase(100, frame=QubitFrame(2))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + mapping = property_set["target_frame_map"] + self.assertEqual(len(mapping), 2) + self.assertEqual(mapping[Qubit(0)], {mf}) + self.assertEqual(mapping[QubitFrame(1)], {mf}) + + def test_with_sub_blocks(self): + """test with sub blocks""" + mf1 = MixedFrame(Qubit(0), QubitFrame(0)) + mf2 = MixedFrame(Qubit(0), QubitFrame(1)) + mf3 = MixedFrame(Qubit(0), QubitFrame(2)) + + sub_block_2 = IrBlock(AlignLeft()) + sub_block_2.append(Play(Constant(100, 0.1), mixed_frame=mf1)) + + sub_block_1 = IrBlock(AlignLeft()) + sub_block_1.append(Play(Constant(100, 0.1), mixed_frame=mf2)) + sub_block_1.append(sub_block_2) + + property_set = {} + analyze_target_frame_pass(sub_block_1, property_set) + mapping = property_set["target_frame_map"] + self.assertEqual(len(mapping), 3) + self.assertEqual(mapping[Qubit(0)], {mf1, mf2}) + self.assertEqual(mapping[QubitFrame(0)], {mf1}) + self.assertEqual(mapping[QubitFrame(1)], {mf2}) + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf3)) + ir_example.append(sub_block_1) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + mapping = property_set["target_frame_map"] + self.assertEqual(len(mapping), 4) + self.assertEqual(mapping[Qubit(0)], {mf1, mf2, mf3}) + self.assertEqual(mapping[QubitFrame(0)], {mf1}) + self.assertEqual(mapping[QubitFrame(1)], {mf2}) + self.assertEqual(mapping[QubitFrame(2)], {mf3}) + + +class TestSequencePassAlignLeft(QiskitTestCase): + """Test sequence_pass with align left""" + + def test_single_instruction(self): + """test with a single instruction""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 2) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 1) in edge_list) + + # TODO: Take care of this weird edge case + # def test_instruction_not_in_mapping(self): + # """test with an instruction which is not in the mapping""" + # + # ir_example = IrBlock(AlignLeft()) + # ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + # ir_example.append(Delay(100, target=Qubit(5))) + # + # property_set = {} + # analyze_target_frame_pass(ir_example, property_set) + # ir_example = sequence_pass(ir_example, property_set) + # edge_list = ir_example.sequence.edge_list() + # self.assertEqual(len(edge_list), 4) + # self.assertTrue((0, 2) in edge_list) + # self.assertTrue((0, 3) in edge_list) + # self.assertTrue((2, 1) in edge_list) + # self.assertTrue((3, 1) in edge_list) + + def test_parallel_instructions(self): + """test with two parallel instructions""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 4) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 1) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((3, 1) in edge_list) + + def test_sequential_instructions(self): + """test with two sequential instructions""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 3) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 3) in edge_list) + self.assertTrue((3, 1) in edge_list) + + def test_pulse_target_instruction_broadcasting_to_children(self): + """test with an instruction which is defined on a PulseTarget and is + broadcasted to several children""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Delay(100, target=Qubit(0))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 5) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 3) in edge_list) + self.assertTrue((2, 4) in edge_list) + self.assertTrue((3, 1) in edge_list) + self.assertTrue((4, 1) in edge_list) + + def test_frame_instruction_broadcasting_to_children(self): + """test with an instruction which is defined on a Frame and is broadcasted to several children""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 5) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 3) in edge_list) + self.assertTrue((2, 4) in edge_list) + self.assertTrue((3, 1) in edge_list) + self.assertTrue((4, 1) in edge_list) + + def test_pulse_target_instruction_dependency(self): + """test with an instruction which is defined on a PulseTarget and depends on + several mixed frames""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) + ir_example.append(Delay(100, target=Qubit(0))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 5) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((2, 4) in edge_list) + self.assertTrue((3, 4) in edge_list) + self.assertTrue((4, 1) in edge_list) + + def test_frame_instruction_dependency(self): + """test with an instruction which is defined on a Frame and depends on several mixed frames""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) + ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 5) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((2, 4) in edge_list) + self.assertTrue((3, 4) in edge_list) + self.assertTrue((4, 1) in edge_list) + + def test_recursion_to_sub_blocks(self): + """test that sequencing is recursively applied to sub blocks""" + + sub_block = IrBlock(AlignLeft()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + + edge_list_sub_block = ir_example.elements()[1].sequence.edge_list() + self.assertEqual(len(edge_list_sub_block), 2) + self.assertTrue((0, 2) in edge_list_sub_block) + self.assertTrue((2, 1) in edge_list_sub_block) + + def test_with_parallel_sub_block(self): + """test with a sub block which doesn't depend on previous instructions""" + + sub_block = IrBlock(AlignLeft()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 4) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((2, 1) in edge_list) + self.assertTrue((3, 1) in edge_list) + + def test_with_simple_sequential_sub_block(self): + """test with a sub block which depends on a single previous instruction""" + + sub_block = IrBlock(AlignLeft()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(sub_block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 5) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((3, 4) in edge_list) + self.assertTrue((2, 1) in edge_list) + self.assertTrue((4, 1) in edge_list) + + def test_with_sequential_sub_block_with_more_dependencies(self): + """test with a sub block which depends on a single previous instruction""" + + sub_block = IrBlock(AlignLeft()) + sub_block.append(Delay(100, target=Qubit(0))) + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + ir_example.append(sub_block) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 8) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((0, 4) in edge_list) + self.assertTrue((2, 5) in edge_list) + self.assertTrue((3, 5) in edge_list) + self.assertTrue((4, 6) in edge_list) + self.assertTrue((5, 1) in edge_list) + self.assertTrue((6, 1) in edge_list) + + +class TestSchedulePassAlignLeft(QiskitTestCase): + """Test schedule_pass with align left""" + + def test_single_instruction(self): + """test with a single instruction""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 100) + + def test_parallel_instructions(self): + """test with two parallel instructions""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 200) + self.assertEqual(ir_example.scheduled_elements()[0][0], 0) + self.assertEqual(ir_example.scheduled_elements()[1][0], 0) + + def test_sequential_instructions(self): + """test with two sequential instructions""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 200) + self.assertEqual(ir_example.scheduled_elements()[0][0], 0) + self.assertEqual(ir_example.scheduled_elements()[1][0], 100) + + def test_multiple_children(self): + """test for a graph where one node has several children""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Delay(100, target=Qubit(0))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 200) + self.assertEqual(ir_example.scheduled_elements()[0][0], 0) + self.assertEqual(ir_example.scheduled_elements()[1][0], 100) + self.assertEqual(ir_example.scheduled_elements()[2][0], 100) + + def test_multiple_parents(self): + """test for a graph where one node has several parents""" + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) + ir_example.append(Delay(100, target=Qubit(0))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 300) + self.assertEqual(ir_example.scheduled_elements()[0][0], 0) + self.assertEqual(ir_example.scheduled_elements()[1][0], 0) + self.assertEqual(ir_example.scheduled_elements()[2][0], 200) + + def test_recursion_to_leading_sub_blocks(self): + """test that scheduling is recursively applied to sub blocks which are first in the order""" + + sub_block = IrBlock(AlignLeft()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + sub_block = ir_example.elements()[1] + # Note that sub blocks are oblivious to their relative timing + self.assertEqual(sub_block.initial_time(), 0) + self.assertEqual(sub_block.final_time(), 100) + self.assertEqual(sub_block.scheduled_elements()[0][0], 0) + + def test_recursion_to_non_leading_sub_blocks(self): + """test that scheduling is recursively applied to sub blocks when they are not first in order""" + + sub_block = IrBlock(AlignLeft()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(sub_block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + sub_block = ir_example.elements()[2] + # Note that sub blocks are oblivious to their relative timing + self.assertEqual(sub_block.initial_time(), 0) + self.assertEqual(sub_block.final_time(), 100) + self.assertEqual(sub_block.scheduled_elements()[0][0], 0) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 200) + + def test_with_parallel_sub_block(self): + """test with a sub block which doesn't depend on previous instructions""" + + sub_block = IrBlock(AlignLeft()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 100) + + def test_with_sequential_sub_block_with_more_dependencies(self): + """test with a sub block which depends on a single previous instruction""" + + sub_block = IrBlock(AlignLeft()) + sub_block.append(Delay(100, target=Qubit(0))) + + ir_example = IrBlock(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + ir_example.append(sub_block) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 300) + self.assertEqual(ir_example.scheduled_elements()[0][0], 0) + self.assertEqual(ir_example.scheduled_elements()[1][0], 0) + self.assertEqual(ir_example.scheduled_elements()[2][0], 0) + self.assertEqual(ir_example.scheduled_elements()[3][0], 200) + self.assertEqual(ir_example.scheduled_elements()[4][0], 100) diff --git a/test/python/pulse/test_pulse_ir.py b/test/python/pulse/test_pulse_ir.py index ef77892d56b2..8030ebc6c5d7 100644 --- a/test/python/pulse/test_pulse_ir.py +++ b/test/python/pulse/test_pulse_ir.py @@ -11,13 +11,11 @@ # that they have been altered from the originals. """Test pulse IR""" -import copy from test import QiskitTestCase from qiskit.pulse import ( Constant, MemorySlot, - PulseError, Play, Delay, Acquire, @@ -27,334 +25,113 @@ ) from qiskit.pulse.ir import ( - IrInstruction, IrBlock, ) -from qiskit.pulse.transforms import AlignSequential, AlignLeft - - -class TestIrInstruction(QiskitTestCase): - """Test IR Instruction""" - - _play_inst = Play(Constant(100, 0.5), channel=DriveChannel(1)) - - def test_instruction_creation(self): - """Test ir instruction creation""" - ir_inst = IrInstruction(self._play_inst) - self.assertEqual(ir_inst.initial_time, None) - self.assertEqual(ir_inst.duration, 100) - self.assertEqual(ir_inst.duration, self._play_inst.duration) - self.assertEqual(ir_inst.instruction, self._play_inst) - - ir_inst = IrInstruction(self._play_inst, initial_time=100) - self.assertEqual(ir_inst.initial_time, 100) - self.assertEqual(ir_inst.final_time, 200) - - def test_instruction_creation_invalid_initial_time(self): - """Test that instructions can't be constructed with invalid initial_time""" - with self.assertRaises(PulseError): - IrInstruction(self._play_inst, initial_time=0.5) - - with self.assertRaises(PulseError): - IrInstruction(self._play_inst, initial_time=-1) - - def test_initial_time_update(self): - """Test that initial_time update is done and validated correctly""" - ir_inst = IrInstruction(self._play_inst) - - ir_inst.initial_time = 50 - self.assertEqual(ir_inst.initial_time, 50) - - with self.assertRaises(PulseError): - ir_inst.initial_time = -10 - with self.assertRaises(PulseError): - ir_inst.initial_time = 0.5 - - def test_instruction_shift_initial_time(self): - """Test that shifting initial_time is done correctly""" - initial_time = 10 - shift = 15 - - ir_inst = IrInstruction(self._play_inst) - - with self.assertRaises(PulseError): - ir_inst.shift_initial_time(shift) - - ir_inst = IrInstruction(self._play_inst, initial_time=initial_time) - ir_inst.shift_initial_time(shift) - - self.assertEqual(ir_inst.initial_time, initial_time + shift) - - with self.assertRaises(PulseError): - ir_inst.shift_initial_time(0.5) - - def test_final_time_unscheduled(self): - """Test that final time is returned as None if unscheduled""" - ir_inst = IrInstruction(self._play_inst) - self.assertEqual(ir_inst.final_time, None) - - def test_repr(self): - """Test repr""" - ir_inst = IrInstruction(self._play_inst) - self.assertEqual( - str(ir_inst), - "IrInstruction(Play(Constant(duration=100, amp=0.5, angle=0.0), DriveChannel(1)), t0=None)", - ) - - ir_inst = IrInstruction(self._play_inst, 100) - self.assertEqual( - str(ir_inst), - "IrInstruction(Play(Constant(duration=100, amp=0.5, angle=0.0), DriveChannel(1)), t0=100)", - ) +from qiskit.pulse.ir.alignments import AlignLeft +from qiskit.pulse.model import QubitFrame, Qubit class TestIrBlock(QiskitTestCase): """Test IrBlock objects""" _delay_inst = Delay(50, channel=DriveChannel(0)) - _play_inst = Play(Constant(100, 0.5), channel=DriveChannel(1)) + _play_inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) _shift_phase_inst = ShiftPhase(0.1, channel=DriveChannel(3)) _acquire_inst = Acquire(200, channel=AcquireChannel(2), mem_slot=MemorySlot(4)) - def ir_creation(self): + def test_ir_creation(self): """Test ir creation""" - ir_example = IrBlock(AlignSequential()) - self.assertEqual(ir_example.alignment, AlignSequential()) - self.assertEqual(len(ir_example), 0) - - def test_add_instruction(self): - """Test adding single instruction""" - - pulse_ir = IrBlock(AlignLeft()) - ir_inst = IrInstruction(self._play_inst) - pulse_ir.add_element(ir_inst) - self.assertEqual(pulse_ir.elements[0], ir_inst) - ir_inst = IrInstruction(self._delay_inst) - pulse_ir.add_element(ir_inst) - self.assertEqual(pulse_ir.elements[1], ir_inst) - self.assertEqual(len(pulse_ir), 2) - - def test_add_instruction_list(self): - """Test adding instruction list""" - - pulse_ir = IrBlock(AlignLeft()) - inst_list = [ - IrInstruction(self._play_inst), - IrInstruction(self._delay_inst), - IrInstruction(self._acquire_inst), - ] - - pulse_ir.add_element(inst_list) - - self.assertEqual(pulse_ir.elements, inst_list) - self.assertEqual(len(pulse_ir), 3) - - def test_add_sub_block(self): - """Test adding sub block""" - - pulse_ir = IrBlock(AlignLeft()) - pulse_ir.add_element(IrInstruction(self._play_inst)) - - block = IrBlock(AlignLeft()) - block.add_element(IrInstruction(self._delay_inst)) - pulse_ir.add_element(block) - - self.assertEqual(pulse_ir.elements[1], block) - self.assertEqual(len(pulse_ir), 2) - - def test_get_initial_time(self): - """Test initial_time is returned correctly""" - - pulse_ir = IrBlock(AlignLeft()) - # Empty IR defaults to None - self.assertEqual(pulse_ir.initial_time, None) - - pulse_ir.add_element(IrInstruction(self._play_inst, initial_time=100)) - pulse_ir.add_element(IrInstruction(self._delay_inst, initial_time=50)) - self.assertEqual(pulse_ir.initial_time, 50) - - # Test recursion initial_time - block = IrBlock(AlignLeft()) - block.add_element(IrInstruction(self._shift_phase_inst, initial_time=20)) - pulse_ir.add_element(block) - self.assertEqual(pulse_ir.initial_time, 20) - - # If any instruction is not scheduled, initial_time is none - pulse_ir.add_element(IrInstruction(self._acquire_inst)) - self.assertEqual(pulse_ir.initial_time, None) - - def test_shift_initial_time(self): - """Test shift initial_time""" - pulse_ir = IrBlock(AlignLeft()) - pulse_ir.add_element(IrInstruction(self._play_inst)) - - # Can't shift initial_time of IR with unscheduled instructions. - with self.assertRaises(PulseError): - pulse_ir.shift_initial_time(100) - - pulse_ir.elements[0].initial_time = 1000 - pulse_ir.add_element(IrInstruction(self._delay_inst, initial_time=500)) - pulse_ir.shift_initial_time(100) - self.assertEqual(pulse_ir.initial_time, 600) - self.assertEqual(pulse_ir.elements[0].initial_time, 1100) - self.assertEqual(pulse_ir.elements[1].initial_time, 600) - - with self.assertRaises(PulseError): - pulse_ir.shift_initial_time(0.5) - - def test_shift_initial_time_with_sub_blocks(self): - """Test shift initial_time with sub blocks""" - pulse_ir = IrBlock(AlignLeft()) - pulse_ir.add_element(IrInstruction(self._play_inst, 100)) - pulse_ir.add_element(IrInstruction(self._play_inst, 200)) - block = copy.deepcopy(pulse_ir) - block.shift_initial_time(1000) - pulse_ir.add_element(block) - pulse_ir.shift_initial_time(100) - self.assertEqual(pulse_ir.elements[0].initial_time, 200) - self.assertEqual(pulse_ir.elements[1].initial_time, 300) - self.assertEqual(pulse_ir.elements[2].elements[0].initial_time, 1200) - self.assertEqual(pulse_ir.elements[2].elements[1].initial_time, 1300) - - def test_get_final_time(self): - """Test final time is returned correctly""" - - pulse_ir = IrBlock(AlignLeft()) - # Empty IR defaults to None - self.assertEqual(pulse_ir.final_time, None) - - pulse_ir.add_element(IrInstruction(self._delay_inst, initial_time=500)) - pulse_ir.add_element(IrInstruction(self._play_inst, initial_time=1000)) - self.assertEqual(pulse_ir.final_time, 1000 + self._play_inst.duration) - - # Recursion final time - block = IrBlock(AlignLeft()) - block.add_element(IrInstruction(self._shift_phase_inst, initial_time=2000)) - pulse_ir.add_element(block) - self.assertEqual(pulse_ir.final_time, 2000) - - # If any instruction is not scheduled, final time is none - pulse_ir.add_element(IrInstruction(self._shift_phase_inst)) - self.assertEqual(pulse_ir.initial_time, None) - - def test_get_duration(self): - """Test that duration is calculated and returned correctly""" - pulse_ir = IrBlock(AlignLeft()) - pulse_ir.add_element( - [ - IrInstruction(self._delay_inst, initial_time=10), - IrInstruction(self._play_inst, initial_time=100), - ] - ) - self.assertEqual(pulse_ir.duration, 190) - - def test_get_duration_with_sub_blocks(self): - """Test that duration is calculated and returned correctly with sub blocks""" - pulse_ir = IrBlock(AlignLeft()) - pulse_ir.add_element( - [ - IrInstruction(self._delay_inst, initial_time=10), - IrInstruction(self._play_inst, initial_time=100), - ] - ) - block = IrBlock(AlignLeft()) - block.add_element(IrInstruction(self._play_inst, 200)) - pulse_ir.add_element(block) - self.assertEqual(pulse_ir.duration, 290) - - def test_get_duration_unscheduled(self): - """Test that duration is returned as None if something is not scheduled""" - pulse_ir = IrBlock(AlignLeft()) - pulse_ir.add_element( - [ - IrInstruction(self._delay_inst, initial_time=10), - IrInstruction(self._play_inst), - ] - ) - self.assertEqual(pulse_ir.duration, None) - - def test_has_child(self): - """Test that has_child_IR method works correctly""" - pulse_ir = IrBlock(AlignLeft()) - self.assertFalse(pulse_ir.has_child_ir()) - - pulse_ir.add_element(IrInstruction(self._shift_phase_inst, initial_time=2000)) - self.assertFalse(pulse_ir.has_child_ir()) - - block = IrBlock(AlignLeft()) - pulse_ir.add_element(block) - self.assertTrue(pulse_ir.has_child_ir()) - - def test_ir_comparison_no_sub_blocks(self): - """Test that ir is compared correctly with no sub blocks""" - pulse_ir = IrBlock(AlignLeft()) - pulse_ir.add_element( - [ - IrInstruction(self._delay_inst, initial_time=10), - IrInstruction(self._play_inst, initial_time=100), - ] - ) - ref = copy.deepcopy(pulse_ir) - self.assertEqual(pulse_ir, ref) - - # One element is different - ref.elements[0] = IrInstruction(self._shift_phase_inst, initial_time=50) - self.assertNotEqual(pulse_ir, ref) - - # Extra element - ref = copy.deepcopy(pulse_ir) - ref.add_element(IrInstruction(self._shift_phase_inst, initial_time=50)) - self.assertNotEqual(pulse_ir, ref) - - # Different alignment - ref = copy.deepcopy(pulse_ir) - ref._alignment = AlignLeft - self.assertNotEqual(pulse_ir, ref) - - def test_ir_comparison_with_sub_blocks(self): - """Test that ir is compared correctly with sub blocks""" - pulse_ir = IrBlock(AlignLeft()) - pulse_ir.add_element( - [ - IrInstruction(self._delay_inst, initial_time=10), - IrInstruction(self._play_inst, initial_time=100), - ] - ) - block = IrBlock(AlignLeft()) - pulse_ir.add_element(block) - - # empty sub block - ref = copy.deepcopy(pulse_ir) - self.assertEqual(pulse_ir, ref) - - # sub block has extra element - ref.elements[2].add_element(IrInstruction(self._shift_phase_inst, initial_time=500)) - self.assertNotEqual(pulse_ir, ref) - - # sub block is identical - pulse_ir.elements[2].add_element(IrInstruction(self._shift_phase_inst, initial_time=500)) - self.assertEqual(pulse_ir, ref) - - def test_repr(self): - """Test repr""" - pulse_ir = IrBlock(AlignLeft()) - pulse_ir.add_element( - [ - IrInstruction(self._play_inst), - IrInstruction(self._play_inst), - IrInstruction(self._play_inst), - ] - ) - self.assertEqual(str(pulse_ir), "IrBlock(AlignLeft(), 3 IrInstructions)") - block = IrBlock(AlignLeft()) - block.add_element(IrInstruction(self._delay_inst, 100)) - pulse_ir.add_element(block) - self.assertEqual(str(pulse_ir), "IrBlock(AlignLeft(), 3 IrInstructions, 1 IrBlocks)") - pulse_ir.elements[0].initial_time = 10 - pulse_ir.elements[1].initial_time = 20 - pulse_ir.elements[2].initial_time = 30 - self.assertEqual( - str(pulse_ir), - "IrBlock(AlignLeft(), 3 IrInstructions, 1 IrBlocks, initial_time=10, duration=140)", - ) + ir_example = IrBlock(AlignLeft()) + self.assertEqual(ir_example.sequence.num_nodes(), 2) + self.assertEqual(ir_example.initial_time(), None) + self.assertEqual(ir_example.final_time(), None) + self.assertEqual(ir_example.duration, None) + + def test_add_elements(self): + """Test addition of elements""" + ir_example = IrBlock(AlignLeft()) + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + ir_example.append(inst) + + self.assertEqual(ir_example.sequence.num_nodes(), 3) + self.assertEqual(ir_example.elements()[0], inst) + + inst = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + ir_example.append(inst) + self.assertEqual(ir_example.sequence.num_nodes(), 4) + self.assertEqual(len(ir_example.elements()), 2) + self.assertEqual(ir_example.elements()[1], inst) + + def test_initial_time(self): + """Test initial time""" + ir_example = IrBlock(AlignLeft()) + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + ir_example.append(inst) + ir_example.append(inst) + ir_example.append(inst) + ir_example._time_table[2] = 100 + ir_example._time_table[3] = 200 + # Just for the sake of the test. The minimal initial time has to have an edge with 0. + ir_example._time_table[4] = 50 + ir_example._sequence.add_edge(0, 2, None) + ir_example._sequence.add_edge(0, 3, None) + self.assertEqual(ir_example.initial_time(), 100) + + ir_example._time_table[3] = 0 + self.assertEqual(ir_example.initial_time(), 0) + + def test_final_time(self): + """Test final time""" + ir_example = IrBlock(AlignLeft()) + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + ir_example.append(inst) + ir_example.append(inst) + ir_example.append(inst) + # Just for the sake of the test. The maximal final time has to have an edge with 1. + ir_example._time_table[2] = 1000 + ir_example._time_table[3] = 100 + ir_example._time_table[4] = 200 + ir_example._sequence.add_edge(3, 1, None) + ir_example._sequence.add_edge(4, 1, None) + self.assertEqual(ir_example.final_time(), 300) + + def test_duration(self): + """Test duration""" + ir_example = IrBlock(AlignLeft()) + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + ir_example.append(inst) + ir_example.append(inst) + ir_example._time_table[2] = 100 + ir_example._time_table[3] = 300 + ir_example._sequence.add_edge(0, 2, None) + ir_example._sequence.add_edge(3, 1, None) + + self.assertEqual(ir_example.initial_time(), 100) + self.assertEqual(ir_example.final_time(), 400) + self.assertEqual(ir_example.duration, 300) + + def test_duration_with_sub_block(self): + """Test duration with sub block""" + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + sub_block = IrBlock(AlignLeft()) + sub_block.append(inst) + sub_block._time_table[2] = 0 + sub_block._sequence.add_edge(0, 2, None) + sub_block._sequence.add_edge(2, 1, None) + + self.assertEqual(sub_block.initial_time(), 0) + self.assertEqual(sub_block.final_time(), 100) + self.assertEqual(sub_block.duration, 100) + + ir_example = IrBlock(AlignLeft()) + ir_example.append(inst) + ir_example.append(sub_block) + ir_example._time_table[2] = 100 + ir_example._time_table[3] = 300 + ir_example._sequence.add_edge(0, 2, None) + ir_example._sequence.add_edge(3, 1, None) + + self.assertEqual(ir_example.initial_time(), 100) + self.assertEqual(ir_example.final_time(), 400) + self.assertEqual(ir_example.duration, 300) From a35abedb196a3c97847fe3b459b786f7354e6331 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:11:21 +0200 Subject: [PATCH 02/15] Add align right, align sequential (sequence+schedule) --- qiskit/pulse/compiler/temp_passes.py | 86 +++- qiskit/pulse/ir/alignments.py | 4 + .../pulse/test_pulse_compiler_passes.py | 411 +++++++++++++++++- 3 files changed, 474 insertions(+), 27 deletions(-) diff --git a/qiskit/pulse/compiler/temp_passes.py b/qiskit/pulse/compiler/temp_passes.py index 72f9227e45a2..5ac6acc9067b 100644 --- a/qiskit/pulse/compiler/temp_passes.py +++ b/qiskit/pulse/compiler/temp_passes.py @@ -13,14 +13,22 @@ """ Temporary Compiler passes """ - +from __future__ import annotations from collections import defaultdict from functools import singledispatch from rustworkx import PyDAG from qiskit.pulse.model import MixedFrame from qiskit.pulse.ir import IrBlock -from qiskit.pulse.ir.alignments import Alignment, AlignLeft +from qiskit.pulse.ir.ir import InNode, OutNode +from qiskit.pulse.ir.alignments import ( + Alignment, + AlignLeft, + ParallelAlignment, + AlignRight, + SequentialAlignment, + AlignSequential, +) def analyze_target_frame_pass(ir_block: IrBlock, property_set) -> None: @@ -62,10 +70,10 @@ def _sequence_instructions(alignment: Alignment, sequence: PyDAG, property_set: raise NotImplementedError -@_sequence_instructions.register(AlignLeft) -def _sequence_left_justfied(alignment, sequence, property_set): - """Finalize the sequence of the IrBlock by recursively adding edges to the DAG, - aligning elements to the left.""" +@_sequence_instructions.register(ParallelAlignment) +def _sequence_parallel(alignment, sequence, property_set): + """Sequence the IrBlock by recursively adding edges to the DAG, + adding elements in parallel to one another in the graph""" idle_after = {} for ni in sequence.node_indices(): if ni in (0, 1): @@ -101,6 +109,20 @@ def _sequence_left_justfied(alignment, sequence, property_set): sequence.add_edges_from_no_data([(ni, 1) for ni in idle_after.values()]) +@_sequence_instructions.register(SequentialAlignment) +def _sequence_sequential(alignment, sequence: PyDAG, property_set): + """Sequence the IrBlock by recursively adding edges to the DAG, + adding elements one after the other""" + sequence.add_edge(0, 2, None) + sequence.add_edge(sequence.num_nodes() - 1, 1, None) + sequence.add_edges_from_no_data([(x, x + 1) for x in range(2, sequence.num_nodes() - 1)]) + + # Apply recursively + for node in sequence.nodes(): + if isinstance(node, IrBlock): + sequence_pass(node, property_set) + + def schedule_pass(ir_block: IrBlock, property_set: dict) -> IrBlock: """Recursively schedule the block by setting initial time for every element""" # mutate graph @@ -115,7 +137,7 @@ def _schedule_elements(alignment, table, sequence, property_set): @_schedule_elements.register(AlignLeft) -def _schedule_left_justfied(alignment, table, sequence: PyDAG, property_set): +def _schedule_left_justified(alignment, table: dict, sequence: PyDAG, property_set: dict): """Recursively schedule the block by setting initial time for every element, aligning elements to the left.""" @@ -144,3 +166,53 @@ def _schedule_left_justfied(alignment, table, sequence: PyDAG, property_set): t0 = max([table.get(pred) + sequence.get_node_data(pred).duration for pred in preds]) table[node_index] = t0 nodes.extend(sequence.successor_indices(node_index)) + + +@_schedule_elements.register(AlignSequential) +def _schedule_sequential(alignment, table: dict, sequence: PyDAG, property_set: dict): + """Recursively schedule the block by setting initial time for every element, + assuming the elements are sequential""" + + # TODO: This placeholder will fail if any change is done to the graph, + # needs a more robust implementation. + + total_time = 0 + + for i in range(2, sequence.num_nodes()): + table[i] = total_time + node = sequence.get_node_data(i) + if isinstance(node, IrBlock): + schedule_pass(node, property_set) + total_time += node.duration + + +@_schedule_elements.register(AlignRight) +def _schedule_right_justified(alignment, table: dict, sequence: PyDAG, property_set: dict) -> None: + """Recursively schedule the block by setting initial time for every element, + aligning elements to the right.""" + + reversed_sequence = sequence.copy() + # Reverse all edge + reversed_sequence.reverse() + # Now swap 0 and 1 nodes + new_start_node_successors = reversed_sequence.successor_indices(1) + new_end_node_predecessors = reversed_sequence.predecessor_indices(0) + reversed_sequence.remove_node(0) + reversed_sequence.remove_node(1) + reversed_sequence.add_node(InNode) + reversed_sequence.add_node(OutNode) + reversed_sequence.add_edges_from_no_data([(0, x) for x in new_start_node_successors]) + reversed_sequence.add_edges_from_no_data([(x, 1) for x in new_end_node_predecessors]) + + # Schedule with left alignment + _schedule_left_justified(alignment, table, reversed_sequence, property_set) + + # Then reverse the timings + total_duration = max( + [ + table[i] + reversed_sequence.get_node_data(i).duration + for i in reversed_sequence.predecessor_indices(1) + ] + ) + for key in table.keys(): + table[key] = total_duration - table[key] - reversed_sequence.get_node_data(key).duration diff --git a/qiskit/pulse/ir/alignments.py b/qiskit/pulse/ir/alignments.py index 9e48d4b21f6b..5cbd9b886ea0 100644 --- a/qiskit/pulse/ir/alignments.py +++ b/qiskit/pulse/ir/alignments.py @@ -29,5 +29,9 @@ class AlignLeft(ParallelAlignment): pass +class AlignRight(ParallelAlignment): + pass + + class AlignSequential(SequentialAlignment): pass diff --git a/test/python/pulse/test_pulse_compiler_passes.py b/test/python/pulse/test_pulse_compiler_passes.py index 3d1c290f816f..7b42aa54d020 100644 --- a/test/python/pulse/test_pulse_compiler_passes.py +++ b/test/python/pulse/test_pulse_compiler_passes.py @@ -11,8 +11,11 @@ # that they have been altered from the originals. """Test compiler passes""" - +import copy from test import QiskitTestCase + +from ddt import ddt, named_data, unpack + from qiskit.pulse import ( Constant, Play, @@ -24,7 +27,13 @@ IrBlock, ) -from qiskit.pulse.ir.alignments import AlignLeft +from qiskit.pulse.ir.alignments import ( + AlignLeft, + AlignRight, + ParallelAlignment, + SequentialAlignment, + AlignSequential, +) from qiskit.pulse.model import QubitFrame, Qubit, MixedFrame from qiskit.pulse.compiler import analyze_target_frame_pass, sequence_pass, schedule_pass @@ -105,13 +114,14 @@ def test_with_sub_blocks(self): self.assertEqual(mapping[QubitFrame(2)], {mf3}) -class TestSequencePassAlignLeft(QiskitTestCase): - """Test sequence_pass with align left""" +@ddt +class TestSequenceParallelAlignment(QiskitTestCase): + """Test sequence_pass with Parallel Alignment""" def test_single_instruction(self): """test with a single instruction""" - ir_example = IrBlock(AlignLeft()) + ir_example = IrBlock(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) property_set = {} @@ -143,7 +153,7 @@ def test_single_instruction(self): def test_parallel_instructions(self): """test with two parallel instructions""" - ir_example = IrBlock(AlignLeft()) + ir_example = IrBlock(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) @@ -160,7 +170,7 @@ def test_parallel_instructions(self): def test_sequential_instructions(self): """test with two sequential instructions""" - ir_example = IrBlock(AlignLeft()) + ir_example = IrBlock(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) @@ -177,7 +187,7 @@ def test_pulse_target_instruction_broadcasting_to_children(self): """test with an instruction which is defined on a PulseTarget and is broadcasted to several children""" - ir_example = IrBlock(AlignLeft()) + ir_example = IrBlock(ParallelAlignment()) ir_example.append(Delay(100, target=Qubit(0))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) @@ -196,7 +206,7 @@ def test_pulse_target_instruction_broadcasting_to_children(self): def test_frame_instruction_broadcasting_to_children(self): """test with an instruction which is defined on a Frame and is broadcasted to several children""" - ir_example = IrBlock(AlignLeft()) + ir_example = IrBlock(ParallelAlignment()) ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) @@ -216,7 +226,7 @@ def test_pulse_target_instruction_dependency(self): """test with an instruction which is defined on a PulseTarget and depends on several mixed frames""" - ir_example = IrBlock(AlignLeft()) + ir_example = IrBlock(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) ir_example.append(Delay(100, target=Qubit(0))) @@ -235,7 +245,7 @@ def test_pulse_target_instruction_dependency(self): def test_frame_instruction_dependency(self): """test with an instruction which is defined on a Frame and depends on several mixed frames""" - ir_example = IrBlock(AlignLeft()) + ir_example = IrBlock(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) @@ -254,10 +264,10 @@ def test_frame_instruction_dependency(self): def test_recursion_to_sub_blocks(self): """test that sequencing is recursively applied to sub blocks""" - sub_block = IrBlock(AlignLeft()) + sub_block = IrBlock(ParallelAlignment()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(AlignLeft()) + ir_example = IrBlock(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) @@ -273,10 +283,10 @@ def test_recursion_to_sub_blocks(self): def test_with_parallel_sub_block(self): """test with a sub block which doesn't depend on previous instructions""" - sub_block = IrBlock(AlignLeft()) + sub_block = IrBlock(ParallelAlignment()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(AlignLeft()) + ir_example = IrBlock(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) @@ -293,10 +303,10 @@ def test_with_parallel_sub_block(self): def test_with_simple_sequential_sub_block(self): """test with a sub block which depends on a single previous instruction""" - sub_block = IrBlock(AlignLeft()) + sub_block = IrBlock(ParallelAlignment()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(AlignLeft()) + ir_example = IrBlock(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(sub_block) @@ -315,10 +325,10 @@ def test_with_simple_sequential_sub_block(self): def test_with_sequential_sub_block_with_more_dependencies(self): """test with a sub block which depends on a single previous instruction""" - sub_block = IrBlock(AlignLeft()) + sub_block = IrBlock(ParallelAlignment()) sub_block.append(Delay(100, target=Qubit(0))) - ir_example = IrBlock(AlignLeft()) + ir_example = IrBlock(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) @@ -339,6 +349,136 @@ def test_with_sequential_sub_block_with_more_dependencies(self): self.assertTrue((5, 1) in edge_list) self.assertTrue((6, 1) in edge_list) + @named_data(["align_left", AlignLeft()], ["align_right", AlignRight()]) + @unpack + def test_specific_alignments(self, alignment): + """Test that specific alignments are the same as parallel alignment""" + + sub_block = IrBlock(ParallelAlignment()) + sub_block.append(Delay(100, target=Qubit(0))) + + ir_example = IrBlock(ParallelAlignment()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + ir_example.append(sub_block) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + + ir_example_specific = copy.deepcopy(ir_example) + ir_example_specific._alignment = alignment + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + + property_set = {} + analyze_target_frame_pass(ir_example_specific, property_set) + ir_example_specific = sequence_pass(ir_example_specific, property_set) + + self.assertEqual(ir_example.sequence.edge_list(), ir_example_specific.sequence.edge_list()) + + +class TestSequenceSequentialAlignment(QiskitTestCase): + """Test sequence_pass with Sequential Alignment""" + + def test_single_instruction(self): + """test with a single instruction""" + + ir_example = IrBlock(SequentialAlignment()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 2) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 1) in edge_list) + + def test_several_instructions(self): + """test with several instructions""" + + ir_example = IrBlock(SequentialAlignment()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(2), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 4) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 3) in edge_list) + self.assertTrue((3, 4) in edge_list) + self.assertTrue((4, 1) in edge_list) + + def test_recursion_to_sub_blocks(self): + """test that sequencing is recursively applied to sub blocks""" + + sub_block = IrBlock(SequentialAlignment()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(SequentialAlignment()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + + edge_list_sub_block = ir_example.elements()[1].sequence.edge_list() + self.assertEqual(len(edge_list_sub_block), 2) + self.assertTrue((0, 2) in edge_list_sub_block) + self.assertTrue((2, 1) in edge_list_sub_block) + + def test_sub_blocks_and_instructions(self): + """test sequencing with a mix of instructions and sub blocks""" + + sub_block = IrBlock(SequentialAlignment()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(SequentialAlignment()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 4) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 3) in edge_list) + self.assertTrue((3, 4) in edge_list) + self.assertTrue((4, 1) in edge_list) + + def test_align_sequential(self): + """test sequencing with AlignSequential""" + + sub_block = IrBlock(SequentialAlignment()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(SequentialAlignment()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) + + ir_example_specific = copy.deepcopy(ir_example) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + + property_set = {} + analyze_target_frame_pass(ir_example_specific, property_set) + ir_example_specific = sequence_pass(ir_example_specific, property_set) + + edge_list = ir_example.sequence.edge_list() + edge_list_specific = ir_example_specific.sequence.edge_list() + self.assertEqual(edge_list, edge_list_specific) + class TestSchedulePassAlignLeft(QiskitTestCase): """Test schedule_pass with align left""" @@ -488,7 +628,7 @@ def test_with_parallel_sub_block(self): self.assertEqual(ir_example.final_time(), 100) def test_with_sequential_sub_block_with_more_dependencies(self): - """test with a sub block which depends on a single previous instruction""" + """test with a sub block which depends on a several previous instruction""" sub_block = IrBlock(AlignLeft()) sub_block.append(Delay(100, target=Qubit(0))) @@ -512,3 +652,234 @@ def test_with_sequential_sub_block_with_more_dependencies(self): self.assertEqual(ir_example.scheduled_elements()[2][0], 0) self.assertEqual(ir_example.scheduled_elements()[3][0], 200) self.assertEqual(ir_example.scheduled_elements()[4][0], 100) + + +class TestSchedulePassAlignRight(QiskitTestCase): + """Test schedule_pass with align right""" + + def test_single_instruction(self): + """test with a single instruction""" + + ir_example = IrBlock(AlignRight()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 100) + + def test_parallel_instructions(self): + """test with two parallel instructions""" + + ir_example = IrBlock(AlignRight()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 200) + self.assertEqual(ir_example.scheduled_elements()[0][0], 100) + self.assertEqual(ir_example.scheduled_elements()[1][0], 0) + + def test_sequential_instructions(self): + """test with two sequential instructions""" + + ir_example = IrBlock(AlignRight()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 200) + self.assertEqual(ir_example.scheduled_elements()[0][0], 0) + self.assertEqual(ir_example.scheduled_elements()[1][0], 100) + + def test_multiple_children(self): + """test for a graph where one node has several children""" + + ir_example = IrBlock(AlignRight()) + ir_example.append(Delay(100, target=Qubit(0))) + ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 300) + self.assertEqual(ir_example.scheduled_elements()[0][0], 0) + self.assertEqual(ir_example.scheduled_elements()[1][0], 100) + self.assertEqual(ir_example.scheduled_elements()[2][0], 200) + + def test_multiple_parents(self): + """test for a graph where one node has several parents""" + + ir_example = IrBlock(AlignRight()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) + ir_example.append(Delay(100, target=Qubit(0))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 300) + self.assertEqual(ir_example.scheduled_elements()[0][0], 100) + self.assertEqual(ir_example.scheduled_elements()[1][0], 0) + self.assertEqual(ir_example.scheduled_elements()[2][0], 200) + + def test_recursion_to_sub_blocks(self): + """test that scheduling is recursively applied to sub blocks which are first in the order""" + + sub_block = IrBlock(AlignRight()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(AlignRight()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + sub_block = ir_example.elements()[1] + # Note that sub blocks are oblivious to their relative timing + self.assertEqual(sub_block.initial_time(), 0) + self.assertEqual(sub_block.final_time(), 100) + self.assertEqual(sub_block.scheduled_elements()[0][0], 0) + + def test_with_parallel_sub_block(self): + """test with a sub block which doesn't depend on previous instructions""" + + sub_block = IrBlock(AlignRight()) + sub_block.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(AlignRight()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 200) + self.assertEqual(ir_example._time_table[2], 100) + self.assertEqual(ir_example._time_table[3], 0) + + def test_with_sequential_sub_block_with_more_dependencies(self): + """test with a sub block which depends on a several previous instruction""" + + sub_block = IrBlock(AlignRight()) + sub_block.append(Delay(100, target=Qubit(0))) + + ir_example = IrBlock(AlignRight()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + ir_example.append(sub_block) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 300) + self.assertEqual(ir_example.scheduled_elements()[0][0], 100) + self.assertEqual(ir_example.scheduled_elements()[1][0], 0) + self.assertEqual(ir_example.scheduled_elements()[2][0], 100) + self.assertEqual(ir_example.scheduled_elements()[3][0], 200) + self.assertEqual(ir_example.scheduled_elements()[4][0], 200) + + +class TestSchedulePassAlignSequential(QiskitTestCase): + """Test schedule_pass with align sequential""" + + def test_single_instruction(self): + """test with a single instruction""" + + ir_example = IrBlock(AlignSequential()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 100) + + def test_several_instructions(self): + """test with several instructions""" + + ir_example = IrBlock(AlignSequential()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 400) + self.assertEqual(ir_example.scheduled_elements()[0][0], 0) + self.assertEqual(ir_example.scheduled_elements()[1][0], 100) + self.assertEqual(ir_example.scheduled_elements()[2][0], 300) + + def test_recursion_to_sub_blocks(self): + """test that scheduling is recursively applied to sub blocks""" + + sub_block = IrBlock(AlignSequential()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = IrBlock(AlignSequential()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + sub_block = ir_example.elements()[1] + # Note that sub blocks are oblivious to their relative timing + self.assertEqual(sub_block.initial_time(), 0) + self.assertEqual(sub_block.final_time(), 100) + self.assertEqual(sub_block.scheduled_elements()[0][0], 0) + + def test_with_instructions_and_sub_blocks(self): + """test that scheduling is recursively applied to sub blocks""" + + sub_block = IrBlock(AlignSequential()) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + + ir_example = IrBlock(AlignSequential()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + self.assertEqual(ir_example.initial_time(), 0) + self.assertEqual(ir_example.final_time(), 400) + self.assertEqual(ir_example.scheduled_elements()[0][0], 0) + self.assertEqual(ir_example.scheduled_elements()[1][0], 100) + self.assertEqual(ir_example.scheduled_elements()[2][0], 300) From 592aba6a3983a1d75aae1b2fab42446c0d581629 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:51:26 +0200 Subject: [PATCH 03/15] Add draw, flatten --- qiskit/pulse/compiler/temp_passes.py | 1 + qiskit/pulse/ir/ir.py | 66 +++++++++++- test/python/pulse/test_pulse_ir.py | 149 +++++++++++++++++++++++++++ 3 files changed, 215 insertions(+), 1 deletion(-) diff --git a/qiskit/pulse/compiler/temp_passes.py b/qiskit/pulse/compiler/temp_passes.py index 5ac6acc9067b..cec4c358ede3 100644 --- a/qiskit/pulse/compiler/temp_passes.py +++ b/qiskit/pulse/compiler/temp_passes.py @@ -126,6 +126,7 @@ def _sequence_sequential(alignment, sequence: PyDAG, property_set): def schedule_pass(ir_block: IrBlock, property_set: dict) -> IrBlock: """Recursively schedule the block by setting initial time for every element""" # mutate graph + # TODO : Check if graph has edges. Override existing edges? Raise Error? _schedule_elements(ir_block.alignment, ir_block._time_table, ir_block._sequence, property_set) return ir_block diff --git a/qiskit/pulse/ir/ir.py b/qiskit/pulse/ir/ir.py index f02fed5c03aa..fb44dc12e8a6 100644 --- a/qiskit/pulse/ir/ir.py +++ b/qiskit/pulse/ir/ir.py @@ -21,8 +21,11 @@ from __future__ import annotations +import copy + import rustworkx as rx from rustworkx import PyDAG +from rustworkx.visualization import graphviz_draw from qiskit.pulse.ir.alignments import Alignment from qiskit.pulse import Instruction @@ -120,7 +123,68 @@ def duration(self) -> int | None: except TypeError: return None + def draw(self, recursive: bool = False): + """Draw the graph of the IrBlock""" + if recursive: + draw_sequence = self.flatten().sequence + else: + draw_sequence = self.sequence + + def _draw_nodes(n): + if n is InNode or n is OutNode: + return {"fillcolor": "grey", "style": "filled"} + try: + name = " " + n.name + except (TypeError, AttributeError): + name = "" + return {"label": f"{n.__class__.__name__}" + name} + + return graphviz_draw( + draw_sequence, + node_attr_fn=_draw_nodes, + ) + + def flatten(self, inplace: bool = False) -> IrBlock: + """Recursively flatten the IrBlock""" + # TODO : Verify that the block\sub blocks are sequenced. + if inplace: + block = self + else: + block = copy.deepcopy(self) + block._sequence[0] = InNode + block._sequence[1] = OutNode + + for ind in block.sequence.node_indices(): + if isinstance(block.sequence.get_node_data(ind), IrBlock): + + def edge_map(x, y, w): + if y == ind: + return 0 + if x == ind: + return 1 + return None + + sub_block = block.sequence.get_node_data(ind) + sub_block.flatten(inplace=True) + initial_time = block._time_table.get(ind, None) + nodes_mapping = block._sequence.substitute_node_with_subgraph( + ind, sub_block.sequence, edge_map + ) + if initial_time is not None: + for old_node in nodes_mapping.keys(): + if old_node not in (0, 1): + block._time_table[nodes_mapping[old_node]] = ( + initial_time + sub_block._time_table[old_node] + ) + block._sequence.remove_node_retain_edges(nodes_mapping[0]) + block._sequence.remove_node_retain_edges(nodes_mapping[1]) + + return block + def __eq__(self, other): if self.alignment != other.alignment: return False - return rx.is_isomorphic_node_match(self._sequence, other._sequence, lambda x, y: x == y) + # TODO : Define alignment equating. Currently this doesn't work. + return rx.is_isomorphic_node_match( + self._sequence, other._sequence, lambda x, y: x == y or x is y + ) diff --git a/test/python/pulse/test_pulse_ir.py b/test/python/pulse/test_pulse_ir.py index 8030ebc6c5d7..10e16a23424f 100644 --- a/test/python/pulse/test_pulse_ir.py +++ b/test/python/pulse/test_pulse_ir.py @@ -30,6 +30,11 @@ from qiskit.pulse.ir.alignments import AlignLeft from qiskit.pulse.model import QubitFrame, Qubit +from qiskit.pulse.compiler.temp_passes import ( + sequence_pass, + schedule_pass, + analyze_target_frame_pass, +) class TestIrBlock(QiskitTestCase): @@ -135,3 +140,147 @@ def test_duration_with_sub_block(self): self.assertEqual(ir_example.initial_time(), 100) self.assertEqual(ir_example.final_time(), 400) self.assertEqual(ir_example.duration, 300) + + def test_flatten_ir_no_sub_blocks(self): + """Test that flattening ir with no sub blocks doesn't do anything""" + ir_example = IrBlock(AlignLeft()) + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + ir_example.append(inst) + ir_example.append(inst2) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + self.assertEqual(ir_example.flatten(), ir_example) + + def test_flatten_inplace_flag(self): + """Test that inplace flag in flattening works""" + ir_example = IrBlock(AlignLeft()) + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + ir_example.append(inst) + ir_example.append(inst2) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + self.assertTrue(ir_example.flatten(inplace=True) is ir_example) + self.assertFalse(ir_example.flatten() is ir_example) + + def test_flatten_one_sub_block(self): + """Test that flattening works with one block""" + ir_example = IrBlock(AlignLeft()) + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + block = IrBlock(AlignLeft()) + block.append(inst) + block.append(inst2) + ir_example.append(block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + flat = ir_example.flatten() + edge_list = flat.sequence.edge_list() + print(edge_list) + self.assertEqual(len(edge_list), 4) + self.assertTrue((0, 5) in edge_list) + self.assertTrue((0, 6) in edge_list) + self.assertTrue((5, 1) in edge_list) + self.assertTrue((6, 1) in edge_list) + + def test_flatten_one_sub_block_and_parallel_instruction(self): + """Test that flattening works with one block and parallel instruction""" + ir_example = IrBlock(AlignLeft()) + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + block = IrBlock(AlignLeft()) + block.append(inst) + block.append(inst2) + ir_example.append(block) + ir_example.append(Play(Constant(100, 0.5), frame=QubitFrame(3), target=Qubit(3))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + flat = ir_example.flatten() + edge_list = flat.sequence.edge_list() + self.assertEqual(len(edge_list), 6) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((3, 1) in edge_list) + self.assertTrue((0, 6) in edge_list) + self.assertTrue((0, 7) in edge_list) + self.assertTrue((6, 1) in edge_list) + self.assertTrue((7, 1) in edge_list) + + def test_flatten_one_sub_block_and_sequential_instructions(self): + """Test that flattening works with one block and sequential instructions""" + ir_example = IrBlock(AlignLeft()) + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + block = IrBlock(AlignLeft()) + block.append(inst) + block.append(inst2) + ir_example.append(Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1))) + ir_example.append(block) + ir_example.append(Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2))) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + flat = ir_example.flatten() + edge_list = flat.sequence.edge_list() + self.assertEqual(len(edge_list), 8) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((4, 1) in edge_list) + self.assertTrue((2, 7) in edge_list) + self.assertTrue((2, 8) in edge_list) + self.assertTrue((7, 1) in edge_list) + self.assertTrue((8, 1) in edge_list) + self.assertTrue((7, 4) in edge_list) + self.assertTrue((8, 4) in edge_list) + + def test_flatten_two_levels(self): + """Test that flattening works with one block and sequential instructions""" + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + + block1 = IrBlock(AlignLeft()) + block1.append(inst) + block = IrBlock(AlignLeft()) + block.append(inst) + block.append(block1) + + ir_example = IrBlock(AlignLeft()) + ir_example.append(inst) + ir_example.append(block) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + flat = ir_example.flatten() + edge_list = flat.sequence.edge_list() + self.assertEqual(len(edge_list), 4) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 6) in edge_list) + self.assertTrue((6, 7) in edge_list) + self.assertTrue((7, 1) in edge_list) + self.assertEqual(flat.scheduled_elements()[0], [0, inst]) + self.assertEqual(flat.scheduled_elements()[1], [100, inst]) + self.assertEqual(flat.scheduled_elements()[2], [200, inst]) + + # TODO : Test IrBlock equating. Problem with Alignment, and possibly InNode,OutNode. + + # TODO : Test IrBlock.draw() From 8c839577e85c67a545296d8eef9f90a12889e70a Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Wed, 6 Mar 2024 01:39:45 +0200 Subject: [PATCH 04/15] Corrections --- qiskit/pulse/compiler/temp_passes.py | 36 ++-- qiskit/pulse/ir/__init__.py | 2 +- qiskit/pulse/ir/alignments.py | 21 +- qiskit/pulse/ir/ir.py | 140 +++++++++----- .../pulse/test_pulse_compiler_passes.py | 124 ++++++------ test/python/pulse/test_pulse_ir.py | 180 +++++++++++++++--- 6 files changed, 341 insertions(+), 162 deletions(-) diff --git a/qiskit/pulse/compiler/temp_passes.py b/qiskit/pulse/compiler/temp_passes.py index cec4c358ede3..1e9363860d7e 100644 --- a/qiskit/pulse/compiler/temp_passes.py +++ b/qiskit/pulse/compiler/temp_passes.py @@ -19,7 +19,7 @@ from rustworkx import PyDAG from qiskit.pulse.model import MixedFrame -from qiskit.pulse.ir import IrBlock +from qiskit.pulse.ir import SequenceIR from qiskit.pulse.ir.ir import InNode, OutNode from qiskit.pulse.ir.alignments import ( Alignment, @@ -31,7 +31,7 @@ ) -def analyze_target_frame_pass(ir_block: IrBlock, property_set) -> None: +def analyze_target_frame_pass(ir_block: SequenceIR, property_set) -> None: """Map the dependency of ``MixedFrame``s on ``PulseTarget`` and ``Frame``. Recursively traverses through the ``ir_block`` to find all ``MixedFrame``s, @@ -45,11 +45,11 @@ def analyze_target_frame_pass(ir_block: IrBlock, property_set) -> None: property_set["target_frame_map"] = dict(target_frame_map) -def _analyze_target_frame_in_block(ir_block: IrBlock, target_frame_map: dict) -> None: +def _analyze_target_frame_in_block(ir_block: SequenceIR, target_frame_map: dict) -> None: """A helper function to recurse through the block while mapping mixed frame dependency""" for elm in ir_block.elements(): # Sub Block - if isinstance(elm, IrBlock): + if isinstance(elm, SequenceIR): _analyze_target_frame_in_block(elm, target_frame_map) # Pulse Instruction else: @@ -58,21 +58,21 @@ def _analyze_target_frame_in_block(ir_block: IrBlock, target_frame_map: dict) -> target_frame_map[elm.inst_target.pulse_target].add(elm.inst_target) -def sequence_pass(ir_block: IrBlock, property_set: dict) -> IrBlock: - """Finalize the sequence of the IrBlock by adding edges to the DAG""" +def sequence_pass(ir_block: SequenceIR, property_set: dict) -> SequenceIR: + """Finalize the sequence of the SequenceIR by adding edges to the DAG""" _sequence_instructions(ir_block.alignment, ir_block._sequence, property_set) return ir_block @singledispatch def _sequence_instructions(alignment: Alignment, sequence: PyDAG, property_set: dict): - """Finalize the sequence of the IrBlock by adding edges to the DAG""" + """Finalize the sequence of the SequenceIR by adding edges to the DAG""" raise NotImplementedError @_sequence_instructions.register(ParallelAlignment) def _sequence_parallel(alignment, sequence, property_set): - """Sequence the IrBlock by recursively adding edges to the DAG, + """Sequence the SequenceIR by recursively adding edges to the DAG, adding elements in parallel to one another in the graph""" idle_after = {} for ni in sequence.node_indices(): @@ -82,7 +82,7 @@ def _sequence_parallel(alignment, sequence, property_set): node = sequence.get_node_data(ni) node_mixed_frames = set() - if isinstance(node, IrBlock): + if isinstance(node, SequenceIR): # Recurse over sub block sequence_pass(node, property_set) inst_targets = node.inst_targets @@ -111,7 +111,7 @@ def _sequence_parallel(alignment, sequence, property_set): @_sequence_instructions.register(SequentialAlignment) def _sequence_sequential(alignment, sequence: PyDAG, property_set): - """Sequence the IrBlock by recursively adding edges to the DAG, + """Sequence the SequenceIR by recursively adding edges to the DAG, adding elements one after the other""" sequence.add_edge(0, 2, None) sequence.add_edge(sequence.num_nodes() - 1, 1, None) @@ -119,12 +119,12 @@ def _sequence_sequential(alignment, sequence: PyDAG, property_set): # Apply recursively for node in sequence.nodes(): - if isinstance(node, IrBlock): + if isinstance(node, SequenceIR): sequence_pass(node, property_set) -def schedule_pass(ir_block: IrBlock, property_set: dict) -> IrBlock: - """Recursively schedule the block by setting initial time for every element""" +def schedule_pass(ir_block: SequenceIR, property_set: dict) -> SequenceIR: + """Recursively schedule the SequenceIR by setting initial time for every element""" # mutate graph # TODO : Check if graph has edges. Override existing edges? Raise Error? _schedule_elements(ir_block.alignment, ir_block._time_table, ir_block._sequence, property_set) @@ -133,13 +133,13 @@ def schedule_pass(ir_block: IrBlock, property_set: dict) -> IrBlock: @singledispatch def _schedule_elements(alignment, table, sequence, property_set): - """Recursively schedule the block by setting initial time for every element""" + """Recursively schedule the SequenceIR by setting initial time for every element""" raise NotImplementedError @_schedule_elements.register(AlignLeft) def _schedule_left_justified(alignment, table: dict, sequence: PyDAG, property_set: dict): - """Recursively schedule the block by setting initial time for every element, + """Recursively schedule the SequenceIR by setting initial time for every element, aligning elements to the left.""" first_nodes = sequence.successor_indices(0) @@ -148,7 +148,7 @@ def _schedule_left_justified(alignment, table: dict, sequence: PyDAG, property_s for node_index in first_nodes: table[node_index] = 0 node = sequence.get_node_data(node_index) - if isinstance(node, IrBlock): + if isinstance(node, SequenceIR): # Recruse over sub blocks schedule_pass(node, property_set) nodes.extend(sequence.successor_indices(node_index)) @@ -159,7 +159,7 @@ def _schedule_left_justified(alignment, table: dict, sequence: PyDAG, property_s # reached end or already scheduled continue node = sequence.get_node_data(node_index) - if isinstance(node, IrBlock): + if isinstance(node, SequenceIR): # Recruse over sub blocks schedule_pass(node, property_set) # Because we go in order, all predecessors are already scheduled @@ -182,7 +182,7 @@ def _schedule_sequential(alignment, table: dict, sequence: PyDAG, property_set: for i in range(2, sequence.num_nodes()): table[i] = total_time node = sequence.get_node_data(i) - if isinstance(node, IrBlock): + if isinstance(node, SequenceIR): schedule_pass(node, property_set) total_time += node.duration diff --git a/qiskit/pulse/ir/__init__.py b/qiskit/pulse/ir/__init__.py index f34ef43dc0ef..776240255237 100644 --- a/qiskit/pulse/ir/__init__.py +++ b/qiskit/pulse/ir/__init__.py @@ -18,4 +18,4 @@ ======================================= """ -from .ir import IrBlock +from .ir import SequenceIR diff --git a/qiskit/pulse/ir/alignments.py b/qiskit/pulse/ir/alignments.py index 5cbd9b886ea0..53b5f7cc7b87 100644 --- a/qiskit/pulse/ir/alignments.py +++ b/qiskit/pulse/ir/alignments.py @@ -9,29 +9,44 @@ # 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. +""" +========= +Pulse IR Alignments +========= +""" class Alignment: - @property - def conditions(self) -> dict: - return {} + """Base abstract class for IR alignments""" + + pass class ParallelAlignment(Alignment): + """Base abstract class for IR alignments which align instructions in parallel""" + pass class SequentialAlignment(Alignment): + """Base abstract class for IR alignments which align instructions sequentially""" + pass class AlignLeft(ParallelAlignment): + """Left Alignment""" + pass class AlignRight(ParallelAlignment): + """Right Alignment""" + pass class AlignSequential(SequentialAlignment): + """Sequential Alignment""" + pass diff --git a/qiskit/pulse/ir/ir.py b/qiskit/pulse/ir/ir.py index fb44dc12e8a6..75d3c23d74ed 100644 --- a/qiskit/pulse/ir/ir.py +++ b/qiskit/pulse/ir/ir.py @@ -16,10 +16,10 @@ ========= Pulse IR ========= - """ from __future__ import annotations +from collections import defaultdict import copy @@ -29,39 +29,38 @@ from qiskit.pulse.ir.alignments import Alignment from qiskit.pulse import Instruction +from qiskit.pulse.exceptions import PulseError InNode = object() OutNode = object() -class IrBlock: +class SequenceIR: """IR representation of instruction sequences - ``IrBlock`` is the backbone of the intermediate representation used in the Qiskit Pulse compiler. - A pulse program is represented as a single ``IrBlock`` object, with elements - which include ``IrInstruction`` objects and other nested ``IrBlock`` objects. + ``SequenceIR`` is the backbone of the intermediate representation used in the Qiskit Pulse compiler. + A pulse program is represented as a single ``SequenceIR`` object, with elements + which include ``IrInstruction`` objects and other nested ``SequenceIR`` objects. """ def __init__(self, alignment: Alignment): self._alignment = alignment - self.time_offset = 0 - self._time_table = {} - self._children = [] + self._time_table = defaultdict(lambda: None) self._sequence = rx.PyDAG(multigraph=False) self._sequence.add_nodes_from([InNode, OutNode]) @property def alignment(self) -> Alignment: - """Return the alignment of the IrBlock""" + """Return the alignment of the SequenceIR""" return self._alignment @property def inst_targets(self) -> set: - """Recursively return a set of all Instruction.inst_target in the IrBlock""" + """Recursively return a set of all Instruction.inst_target in the SequenceIR""" inst_targets = set() for elm in self.elements(): - if isinstance(elm, IrBlock): + if isinstance(elm, SequenceIR): inst_targets |= elm.inst_targets else: inst_targets.add(elm.inst_target) @@ -69,36 +68,71 @@ def inst_targets(self) -> set: @property def sequence(self) -> PyDAG: - """Return the DAG sequence of the IrBlock""" + """Return the DAG sequence of the SequenceIR""" return self._sequence - def append(self, element: IrBlock | Instruction) -> None: - """Append element to the IrBlock""" - new_node_id = self._sequence.add_node(element) - if isinstance(element, IrBlock): - self._children.append(new_node_id) + def append(self, element: SequenceIR | Instruction) -> int: + """Append element to the SequenceIR + + Returns: The index of the added element in the sequence. + """ + return self._sequence.add_node(element) - def elements(self) -> list[IrBlock | Instruction]: - """Return a list of all elements in the IrBlock""" + def elements(self) -> list[SequenceIR | Instruction]: + """Return a list of all elements in the SequenceIR""" return self._sequence.nodes()[2:] - def scheduled_elements(self) -> list[list[int | None, IrBlock | Instruction]]: + def scheduled_elements( + self, recursive: bool = False + ) -> list[tuple[int | None, SequenceIR | Instruction]]: """Return a list of scheduled elements. Each element in the list is [initial_time, element]. """ - return [ - [self._time_table.get(ni, None), self._sequence.get_node_data(ni)] - for ni in self._sequence.node_indices() - if ni not in (0, 1) - ] + if recursive: + return self._recursive_scheduled_elements(0) + else: + return [ + (self._time_table[ind], self._sequence.get_node_data(ind)) + for ind in self._sequence.node_indices() + if ind not in (0, 1) + ] + + def _recursive_scheduled_elements( + self, time_offset: int + ) -> list[tuple[int | None, SequenceIR | Instruction]]: + """Helper function to recursively return the scheduled elements. + + The absolute timing is tracked via the `time_offset`` argument, which represents the initial time + of the block itself. + + Each element in the list is a tuple (initial_time, element). + """ + scheduled_elements = [] + for ind in self._sequence.node_indices(): + if ind not in [0, 1]: + node = self._sequence.get_node_data(ind) + try: + time = self._time_table[ind] + time_offset + except TypeError as ex: + raise PulseError( + "Can not return recursive list of scheduled elements" + " if sub blocks are not scheduled." + ) from ex + + if isinstance(node, SequenceIR): + scheduled_elements.extend(node._recursive_scheduled_elements(time)) + else: + scheduled_elements.append((time, node)) + + return scheduled_elements def initial_time(self) -> int | None: """Return initial time""" first_nodes = self._sequence.successor_indices(0) if not first_nodes: return None - return min([self._time_table.get(node, None) for node in first_nodes], default=None) + return min([self._time_table[ind] for ind in first_nodes], default=None) def final_time(self) -> int | None: """Return final time""" @@ -106,9 +140,9 @@ def final_time(self) -> int | None: if not last_nodes: return None tf = None - for ni in last_nodes: - if (t0 := self._time_table.get(ni, None)) is not None: - duration = self._sequence.get_node_data(ni).duration + for ind in last_nodes: + if (t0 := self._time_table[ind]) is not None: + duration = self._sequence.get_node_data(ind).duration if tf is None: tf = t0 + duration else: @@ -117,14 +151,14 @@ def final_time(self) -> int | None: @property def duration(self) -> int | None: - """Return the duration of the IrBlock""" + """Return the duration of the SequenceIR""" try: return self.final_time() - self.initial_time() except TypeError: return None def draw(self, recursive: bool = False): - """Draw the graph of the IrBlock""" + """Draw the graph of the SequenceIR""" if recursive: draw_sequence = self.flatten().sequence else: @@ -144,31 +178,31 @@ def _draw_nodes(n): node_attr_fn=_draw_nodes, ) - def flatten(self, inplace: bool = False) -> IrBlock: - """Recursively flatten the IrBlock""" - # TODO : Verify that the block\sub blocks are sequenced. + def flatten(self, inplace: bool = False) -> SequenceIR: + """Recursively flatten the SequenceIR""" + # TODO : Verify that the block\sub blocks are sequenced correctly. if inplace: block = self else: block = copy.deepcopy(self) block._sequence[0] = InNode block._sequence[1] = OutNode + # TODO : Move this to __deepcopy__ - for ind in block.sequence.node_indices(): - if isinstance(block.sequence.get_node_data(ind), IrBlock): - - def edge_map(x, y, w): - if y == ind: - return 0 - if x == ind: - return 1 - return None + def edge_map(x, y, node): + if y == node: + return 0 + if x == node: + return 1 + return None + for ind in block.sequence.node_indices(): + if isinstance(block.sequence.get_node_data(ind), SequenceIR): sub_block = block.sequence.get_node_data(ind) sub_block.flatten(inplace=True) - initial_time = block._time_table.get(ind, None) + initial_time = block._time_table[ind] nodes_mapping = block._sequence.substitute_node_with_subgraph( - ind, sub_block.sequence, edge_map + ind, sub_block.sequence, lambda x, y, _: edge_map(x, y, ind) ) if initial_time is not None: for old_node in nodes_mapping.keys(): @@ -181,10 +215,14 @@ def edge_map(x, y, w): return block - def __eq__(self, other): - if self.alignment != other.alignment: + def __eq__(self, other: SequenceIR): + if not isinstance(other.alignment, type(self.alignment)): return False - # TODO : Define alignment equating. Currently this doesn't work. - return rx.is_isomorphic_node_match( - self._sequence, other._sequence, lambda x, y: x == y or x is y - ) + # TODO : This is a temporary setup until we figure out the + # alignment hierarchy and set equating there. + return rx.is_isomorphic_node_match(self._sequence, other._sequence, lambda x, y: x == y) + + # TODO : What about the time_table? The isomorphic comparison allows for the indices + # to be different, But then it's not straightforward to compare the time_table. + # It is reasonable to assume that blocks with the same alignment and the same sequence + # will result in the same time_table, but this decision should be made consciously. diff --git a/test/python/pulse/test_pulse_compiler_passes.py b/test/python/pulse/test_pulse_compiler_passes.py index 7b42aa54d020..cc651d9e9ccb 100644 --- a/test/python/pulse/test_pulse_compiler_passes.py +++ b/test/python/pulse/test_pulse_compiler_passes.py @@ -24,7 +24,7 @@ ) from qiskit.pulse.ir import ( - IrBlock, + SequenceIR, ) from qiskit.pulse.ir.alignments import ( @@ -43,7 +43,7 @@ class TestAnalyzeTargetFramePass(QiskitTestCase): def test_basic_ir(self): """test with basic IR""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) mf = MixedFrame(Qubit(0), QubitFrame(1)) ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf)) @@ -66,7 +66,7 @@ def test_basic_ir(self): def test_with_several_inst_target_types(self): """test with different inst_target types""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) mf = MixedFrame(Qubit(0), QubitFrame(1)) ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf)) ir_example.append(Delay(100, target=Qubit(2))) @@ -85,10 +85,10 @@ def test_with_sub_blocks(self): mf2 = MixedFrame(Qubit(0), QubitFrame(1)) mf3 = MixedFrame(Qubit(0), QubitFrame(2)) - sub_block_2 = IrBlock(AlignLeft()) + sub_block_2 = SequenceIR(AlignLeft()) sub_block_2.append(Play(Constant(100, 0.1), mixed_frame=mf1)) - sub_block_1 = IrBlock(AlignLeft()) + sub_block_1 = SequenceIR(AlignLeft()) sub_block_1.append(Play(Constant(100, 0.1), mixed_frame=mf2)) sub_block_1.append(sub_block_2) @@ -100,7 +100,7 @@ def test_with_sub_blocks(self): self.assertEqual(mapping[QubitFrame(0)], {mf1}) self.assertEqual(mapping[QubitFrame(1)], {mf2}) - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf3)) ir_example.append(sub_block_1) @@ -121,7 +121,7 @@ class TestSequenceParallelAlignment(QiskitTestCase): def test_single_instruction(self): """test with a single instruction""" - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) property_set = {} @@ -136,7 +136,7 @@ def test_single_instruction(self): # def test_instruction_not_in_mapping(self): # """test with an instruction which is not in the mapping""" # - # ir_example = IrBlock(AlignLeft()) + # ir_example = SequenceIR(AlignLeft()) # ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) # ir_example.append(Delay(100, target=Qubit(5))) # @@ -153,7 +153,7 @@ def test_single_instruction(self): def test_parallel_instructions(self): """test with two parallel instructions""" - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) @@ -170,7 +170,7 @@ def test_parallel_instructions(self): def test_sequential_instructions(self): """test with two sequential instructions""" - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) @@ -187,7 +187,7 @@ def test_pulse_target_instruction_broadcasting_to_children(self): """test with an instruction which is defined on a PulseTarget and is broadcasted to several children""" - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(Delay(100, target=Qubit(0))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) @@ -206,7 +206,7 @@ def test_pulse_target_instruction_broadcasting_to_children(self): def test_frame_instruction_broadcasting_to_children(self): """test with an instruction which is defined on a Frame and is broadcasted to several children""" - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) @@ -226,7 +226,7 @@ def test_pulse_target_instruction_dependency(self): """test with an instruction which is defined on a PulseTarget and depends on several mixed frames""" - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) ir_example.append(Delay(100, target=Qubit(0))) @@ -245,7 +245,7 @@ def test_pulse_target_instruction_dependency(self): def test_frame_instruction_dependency(self): """test with an instruction which is defined on a Frame and depends on several mixed frames""" - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) @@ -264,10 +264,10 @@ def test_frame_instruction_dependency(self): def test_recursion_to_sub_blocks(self): """test that sequencing is recursively applied to sub blocks""" - sub_block = IrBlock(ParallelAlignment()) + sub_block = SequenceIR(ParallelAlignment()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) @@ -283,10 +283,10 @@ def test_recursion_to_sub_blocks(self): def test_with_parallel_sub_block(self): """test with a sub block which doesn't depend on previous instructions""" - sub_block = IrBlock(ParallelAlignment()) + sub_block = SequenceIR(ParallelAlignment()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) @@ -303,10 +303,10 @@ def test_with_parallel_sub_block(self): def test_with_simple_sequential_sub_block(self): """test with a sub block which depends on a single previous instruction""" - sub_block = IrBlock(ParallelAlignment()) + sub_block = SequenceIR(ParallelAlignment()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(sub_block) @@ -325,10 +325,10 @@ def test_with_simple_sequential_sub_block(self): def test_with_sequential_sub_block_with_more_dependencies(self): """test with a sub block which depends on a single previous instruction""" - sub_block = IrBlock(ParallelAlignment()) + sub_block = SequenceIR(ParallelAlignment()) sub_block.append(Delay(100, target=Qubit(0))) - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) @@ -354,10 +354,10 @@ def test_with_sequential_sub_block_with_more_dependencies(self): def test_specific_alignments(self, alignment): """Test that specific alignments are the same as parallel alignment""" - sub_block = IrBlock(ParallelAlignment()) + sub_block = SequenceIR(ParallelAlignment()) sub_block.append(Delay(100, target=Qubit(0))) - ir_example = IrBlock(ParallelAlignment()) + ir_example = SequenceIR(ParallelAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) @@ -384,7 +384,7 @@ class TestSequenceSequentialAlignment(QiskitTestCase): def test_single_instruction(self): """test with a single instruction""" - ir_example = IrBlock(SequentialAlignment()) + ir_example = SequenceIR(SequentialAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) property_set = {} @@ -398,7 +398,7 @@ def test_single_instruction(self): def test_several_instructions(self): """test with several instructions""" - ir_example = IrBlock(SequentialAlignment()) + ir_example = SequenceIR(SequentialAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(2), QubitFrame(1)))) @@ -416,10 +416,10 @@ def test_several_instructions(self): def test_recursion_to_sub_blocks(self): """test that sequencing is recursively applied to sub blocks""" - sub_block = IrBlock(SequentialAlignment()) + sub_block = SequenceIR(SequentialAlignment()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(SequentialAlignment()) + ir_example = SequenceIR(SequentialAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) @@ -435,10 +435,10 @@ def test_recursion_to_sub_blocks(self): def test_sub_blocks_and_instructions(self): """test sequencing with a mix of instructions and sub blocks""" - sub_block = IrBlock(SequentialAlignment()) + sub_block = SequenceIR(SequentialAlignment()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(SequentialAlignment()) + ir_example = SequenceIR(SequentialAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) @@ -457,10 +457,10 @@ def test_sub_blocks_and_instructions(self): def test_align_sequential(self): """test sequencing with AlignSequential""" - sub_block = IrBlock(SequentialAlignment()) + sub_block = SequenceIR(SequentialAlignment()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(SequentialAlignment()) + ir_example = SequenceIR(SequentialAlignment()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) @@ -486,7 +486,7 @@ class TestSchedulePassAlignLeft(QiskitTestCase): def test_single_instruction(self): """test with a single instruction""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) property_set = {} @@ -499,7 +499,7 @@ def test_single_instruction(self): def test_parallel_instructions(self): """test with two parallel instructions""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) @@ -515,7 +515,7 @@ def test_parallel_instructions(self): def test_sequential_instructions(self): """test with two sequential instructions""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) @@ -531,7 +531,7 @@ def test_sequential_instructions(self): def test_multiple_children(self): """test for a graph where one node has several children""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(Delay(100, target=Qubit(0))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) @@ -549,7 +549,7 @@ def test_multiple_children(self): def test_multiple_parents(self): """test for a graph where one node has several parents""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) ir_example.append(Delay(100, target=Qubit(0))) @@ -567,10 +567,10 @@ def test_multiple_parents(self): def test_recursion_to_leading_sub_blocks(self): """test that scheduling is recursively applied to sub blocks which are first in the order""" - sub_block = IrBlock(AlignLeft()) + sub_block = SequenceIR(AlignLeft()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) @@ -588,10 +588,10 @@ def test_recursion_to_leading_sub_blocks(self): def test_recursion_to_non_leading_sub_blocks(self): """test that scheduling is recursively applied to sub blocks when they are not first in order""" - sub_block = IrBlock(AlignLeft()) + sub_block = SequenceIR(AlignLeft()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(sub_block) @@ -612,10 +612,10 @@ def test_recursion_to_non_leading_sub_blocks(self): def test_with_parallel_sub_block(self): """test with a sub block which doesn't depend on previous instructions""" - sub_block = IrBlock(AlignLeft()) + sub_block = SequenceIR(AlignLeft()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) @@ -630,10 +630,10 @@ def test_with_parallel_sub_block(self): def test_with_sequential_sub_block_with_more_dependencies(self): """test with a sub block which depends on a several previous instruction""" - sub_block = IrBlock(AlignLeft()) + sub_block = SequenceIR(AlignLeft()) sub_block.append(Delay(100, target=Qubit(0))) - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) @@ -660,7 +660,7 @@ class TestSchedulePassAlignRight(QiskitTestCase): def test_single_instruction(self): """test with a single instruction""" - ir_example = IrBlock(AlignRight()) + ir_example = SequenceIR(AlignRight()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) property_set = {} @@ -673,7 +673,7 @@ def test_single_instruction(self): def test_parallel_instructions(self): """test with two parallel instructions""" - ir_example = IrBlock(AlignRight()) + ir_example = SequenceIR(AlignRight()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) @@ -689,7 +689,7 @@ def test_parallel_instructions(self): def test_sequential_instructions(self): """test with two sequential instructions""" - ir_example = IrBlock(AlignRight()) + ir_example = SequenceIR(AlignRight()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) @@ -705,7 +705,7 @@ def test_sequential_instructions(self): def test_multiple_children(self): """test for a graph where one node has several children""" - ir_example = IrBlock(AlignRight()) + ir_example = SequenceIR(AlignRight()) ir_example.append(Delay(100, target=Qubit(0))) ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) @@ -723,7 +723,7 @@ def test_multiple_children(self): def test_multiple_parents(self): """test for a graph where one node has several parents""" - ir_example = IrBlock(AlignRight()) + ir_example = SequenceIR(AlignRight()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) ir_example.append(Delay(100, target=Qubit(0))) @@ -741,10 +741,10 @@ def test_multiple_parents(self): def test_recursion_to_sub_blocks(self): """test that scheduling is recursively applied to sub blocks which are first in the order""" - sub_block = IrBlock(AlignRight()) + sub_block = SequenceIR(AlignRight()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(AlignRight()) + ir_example = SequenceIR(AlignRight()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) @@ -762,10 +762,10 @@ def test_recursion_to_sub_blocks(self): def test_with_parallel_sub_block(self): """test with a sub block which doesn't depend on previous instructions""" - sub_block = IrBlock(AlignRight()) + sub_block = SequenceIR(AlignRight()) sub_block.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(AlignRight()) + ir_example = SequenceIR(AlignRight()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) @@ -782,10 +782,10 @@ def test_with_parallel_sub_block(self): def test_with_sequential_sub_block_with_more_dependencies(self): """test with a sub block which depends on a several previous instruction""" - sub_block = IrBlock(AlignRight()) + sub_block = SequenceIR(AlignRight()) sub_block.append(Delay(100, target=Qubit(0))) - ir_example = IrBlock(AlignRight()) + ir_example = SequenceIR(AlignRight()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) @@ -812,7 +812,7 @@ class TestSchedulePassAlignSequential(QiskitTestCase): def test_single_instruction(self): """test with a single instruction""" - ir_example = IrBlock(AlignSequential()) + ir_example = SequenceIR(AlignSequential()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) property_set = {} @@ -825,7 +825,7 @@ def test_single_instruction(self): def test_several_instructions(self): """test with several instructions""" - ir_example = IrBlock(AlignSequential()) + ir_example = SequenceIR(AlignSequential()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) @@ -843,10 +843,10 @@ def test_several_instructions(self): def test_recursion_to_sub_blocks(self): """test that scheduling is recursively applied to sub blocks""" - sub_block = IrBlock(AlignSequential()) + sub_block = SequenceIR(AlignSequential()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = IrBlock(AlignSequential()) + ir_example = SequenceIR(AlignSequential()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) @@ -864,11 +864,11 @@ def test_recursion_to_sub_blocks(self): def test_with_instructions_and_sub_blocks(self): """test that scheduling is recursively applied to sub blocks""" - sub_block = IrBlock(AlignSequential()) + sub_block = SequenceIR(AlignSequential()) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example = IrBlock(AlignSequential()) + ir_example = SequenceIR(AlignSequential()) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) diff --git a/test/python/pulse/test_pulse_ir.py b/test/python/pulse/test_pulse_ir.py index 10e16a23424f..cacf5658c414 100644 --- a/test/python/pulse/test_pulse_ir.py +++ b/test/python/pulse/test_pulse_ir.py @@ -25,20 +25,21 @@ ) from qiskit.pulse.ir import ( - IrBlock, + SequenceIR, ) -from qiskit.pulse.ir.alignments import AlignLeft +from qiskit.pulse.ir.alignments import AlignLeft, AlignRight from qiskit.pulse.model import QubitFrame, Qubit from qiskit.pulse.compiler.temp_passes import ( sequence_pass, schedule_pass, analyze_target_frame_pass, ) +from qiskit.pulse.exceptions import PulseError -class TestIrBlock(QiskitTestCase): - """Test IrBlock objects""" +class TestSequenceIR(QiskitTestCase): + """Test SequenceIR objects""" _delay_inst = Delay(50, channel=DriveChannel(0)) _play_inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) @@ -47,7 +48,7 @@ class TestIrBlock(QiskitTestCase): def test_ir_creation(self): """Test ir creation""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) self.assertEqual(ir_example.sequence.num_nodes(), 2) self.assertEqual(ir_example.initial_time(), None) self.assertEqual(ir_example.final_time(), None) @@ -55,7 +56,7 @@ def test_ir_creation(self): def test_add_elements(self): """Test addition of elements""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) ir_example.append(inst) @@ -70,7 +71,7 @@ def test_add_elements(self): def test_initial_time(self): """Test initial time""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) ir_example.append(inst) ir_example.append(inst) @@ -88,7 +89,7 @@ def test_initial_time(self): def test_final_time(self): """Test final time""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) ir_example.append(inst) ir_example.append(inst) @@ -103,7 +104,7 @@ def test_final_time(self): def test_duration(self): """Test duration""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) ir_example.append(inst) ir_example.append(inst) @@ -119,7 +120,7 @@ def test_duration(self): def test_duration_with_sub_block(self): """Test duration with sub block""" inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - sub_block = IrBlock(AlignLeft()) + sub_block = SequenceIR(AlignLeft()) sub_block.append(inst) sub_block._time_table[2] = 0 sub_block._sequence.add_edge(0, 2, None) @@ -129,7 +130,7 @@ def test_duration_with_sub_block(self): self.assertEqual(sub_block.final_time(), 100) self.assertEqual(sub_block.duration, 100) - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(inst) ir_example.append(sub_block) ir_example._time_table[2] = 100 @@ -141,9 +142,63 @@ def test_duration_with_sub_block(self): self.assertEqual(ir_example.final_time(), 400) self.assertEqual(ir_example.duration, 300) + def test_scheduled_elements_no_recursion(self): + """Test that scheduled elements with no recursion works""" + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + sub_block = SequenceIR(AlignLeft()) + sub_block.append(inst) + + ir_example = SequenceIR(AlignRight()) + ir_example.append(inst) + ir_example.append(sub_block) + + sch_elements = ir_example.scheduled_elements() + self.assertEqual(len(sch_elements), 2) + self.assertEqual(sch_elements[0], (None, inst)) + self.assertEqual(sch_elements[1], (None, sub_block)) + + def test_scheduled_elements_with_recursion(self): + """Test that scheduled elements with recursion works""" + inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + sub_block2 = SequenceIR(AlignLeft()) + sub_block2.append(inst2) + sub_block2._time_table[2] = 0 + + inst1 = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + sub_block1 = SequenceIR(AlignLeft()) + sub_block1.append(inst1) + sub_block1.append(sub_block2) + sub_block1._time_table[2] = 0 + sub_block1._time_table[3] = 100 + + inst = Play(Constant(100, 0.5), frame=QubitFrame(3), target=Qubit(3)) + ir_example = SequenceIR(AlignRight()) + ir_example.append(inst) + ir_example.append(sub_block1) + ir_example._time_table[2] = 0 + ir_example._time_table[3] = 100 + + sch_elements = ir_example.scheduled_elements(recursive=True) + self.assertEqual(len(sch_elements), 3) + self.assertTrue((0, inst) in sch_elements) + self.assertTrue((100, inst1) in sch_elements) + self.assertTrue((200, inst2) in sch_elements) + + def test_scheduled_elements_with_recursion_raises_error(self): + """Test that scheduled elements with recursion raises error if sub block is not scheduled""" + inst1 = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + sub_block1 = SequenceIR(AlignLeft()) + sub_block1.append(inst1) + + ir_example = SequenceIR(AlignLeft()) + ir_example.append(sub_block1) + + with self.assertRaises(PulseError): + ir_example.scheduled_elements(recursive=True) + def test_flatten_ir_no_sub_blocks(self): """Test that flattening ir with no sub blocks doesn't do anything""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) ir_example.append(inst) @@ -158,7 +213,7 @@ def test_flatten_ir_no_sub_blocks(self): def test_flatten_inplace_flag(self): """Test that inplace flag in flattening works""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) ir_example.append(inst) @@ -174,10 +229,10 @@ def test_flatten_inplace_flag(self): def test_flatten_one_sub_block(self): """Test that flattening works with one block""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) - block = IrBlock(AlignLeft()) + block = SequenceIR(AlignLeft()) block.append(inst) block.append(inst2) ir_example.append(block) @@ -198,10 +253,10 @@ def test_flatten_one_sub_block(self): def test_flatten_one_sub_block_and_parallel_instruction(self): """Test that flattening works with one block and parallel instruction""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) - block = IrBlock(AlignLeft()) + block = SequenceIR(AlignLeft()) block.append(inst) block.append(inst2) ir_example.append(block) @@ -224,10 +279,10 @@ def test_flatten_one_sub_block_and_parallel_instruction(self): def test_flatten_one_sub_block_and_sequential_instructions(self): """Test that flattening works with one block and sequential instructions""" - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) - block = IrBlock(AlignLeft()) + block = SequenceIR(AlignLeft()) block.append(inst) block.append(inst2) ir_example.append(Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1))) @@ -251,17 +306,48 @@ def test_flatten_one_sub_block_and_sequential_instructions(self): self.assertTrue((7, 4) in edge_list) self.assertTrue((8, 4) in edge_list) + def test_flatten_two_sub_blocks(self): + """Test that flattening works with two sub blocks""" + inst1 = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + inst2 = Play(Constant(200, 0.5), frame=QubitFrame(1), target=Qubit(1)) + + block1 = SequenceIR(AlignLeft()) + block1.append(inst1) + block2 = SequenceIR(AlignLeft()) + block2.append(inst2) + + ir_example = SequenceIR(AlignLeft()) + ir_example.append(block1) + ir_example.append(block2) + + property_set = {} + analyze_target_frame_pass(ir_example, property_set) + ir_example = sequence_pass(ir_example, property_set) + ir_example = schedule_pass(ir_example, property_set) + + flat = ir_example.flatten() + ref = SequenceIR(AlignLeft()) + ref.append(inst1) + ref.append(inst2) + + property_set = {} + analyze_target_frame_pass(ref, property_set) + ref = sequence_pass(ref, property_set) + ref = schedule_pass(ref, property_set) + + self.assertEqual(flat, ref) + def test_flatten_two_levels(self): """Test that flattening works with one block and sequential instructions""" inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - block1 = IrBlock(AlignLeft()) + block1 = SequenceIR(AlignLeft()) block1.append(inst) - block = IrBlock(AlignLeft()) + block = SequenceIR(AlignLeft()) block.append(inst) block.append(block1) - ir_example = IrBlock(AlignLeft()) + ir_example = SequenceIR(AlignLeft()) ir_example.append(inst) ir_example.append(block) @@ -277,10 +363,50 @@ def test_flatten_two_levels(self): self.assertTrue((2, 6) in edge_list) self.assertTrue((6, 7) in edge_list) self.assertTrue((7, 1) in edge_list) - self.assertEqual(flat.scheduled_elements()[0], [0, inst]) - self.assertEqual(flat.scheduled_elements()[1], [100, inst]) - self.assertEqual(flat.scheduled_elements()[2], [200, inst]) + self.assertEqual(flat.scheduled_elements()[0], (0, inst)) + self.assertEqual(flat.scheduled_elements()[1], (100, inst)) + self.assertEqual(flat.scheduled_elements()[2], (200, inst)) + + def test_ir_equating_different_alignment(self): + """Test equating of blocks with different alignment""" + self.assertFalse(SequenceIR(AlignLeft()) == SequenceIR(AlignRight())) + + def test_ir_equating_different_instructions(self): + """Test equating of blocks with different instructions""" + inst1 = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(1)) + + ir1 = SequenceIR(AlignLeft()) + ir1.append(inst1) + ir2 = SequenceIR(AlignLeft()) + ir2.append(inst2) + self.assertFalse(ir1 == ir2) + + def test_ir_equating_different_ordering(self): + """Test equating of blocks with different ordering, but the same sequence structure""" + inst1 = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + + ir1 = SequenceIR(AlignLeft()) + ir1.append(inst1) + ir1.append(inst2) + + ir2 = SequenceIR(AlignLeft()) + ir2.append(inst2) + ir2.append(inst1) + + self.assertTrue(ir1 == ir2) + + property_set = {} + analyze_target_frame_pass(ir1, property_set) + ir1 = sequence_pass(ir1, property_set) + ir1 = schedule_pass(ir1, property_set) + + property_set = {} + analyze_target_frame_pass(ir2, property_set) + ir2 = sequence_pass(ir2, property_set) + ir2 = schedule_pass(ir2, property_set) - # TODO : Test IrBlock equating. Problem with Alignment, and possibly InNode,OutNode. + self.assertTrue(ir1 == ir2) - # TODO : Test IrBlock.draw() + # TODO : Test SequenceIR.draw() From b670356c0ddf861dbfd05a184d5b0b0bcca7d945 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Wed, 6 Mar 2024 01:47:11 +0200 Subject: [PATCH 05/15] Split into separate IR PR (temporary remove tests which rely on passes) --- qiskit/pulse/compiler/__init__.py | 16 - qiskit/pulse/compiler/temp_passes.py | 219 ----- .../pulse/test_pulse_compiler_passes.py | 885 ------------------ test/python/pulse/test_pulse_ir.py | 398 ++++---- 4 files changed, 197 insertions(+), 1321 deletions(-) delete mode 100644 qiskit/pulse/compiler/__init__.py delete mode 100644 qiskit/pulse/compiler/temp_passes.py delete mode 100644 test/python/pulse/test_pulse_compiler_passes.py diff --git a/qiskit/pulse/compiler/__init__.py b/qiskit/pulse/compiler/__init__.py deleted file mode 100644 index eaecc16c0312..000000000000 --- a/qiskit/pulse/compiler/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2024. -# -# 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. - -""" -Temporary Compiler folder -""" -from .temp_passes import analyze_target_frame_pass, sequence_pass, schedule_pass diff --git a/qiskit/pulse/compiler/temp_passes.py b/qiskit/pulse/compiler/temp_passes.py deleted file mode 100644 index 1e9363860d7e..000000000000 --- a/qiskit/pulse/compiler/temp_passes.py +++ /dev/null @@ -1,219 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2024. -# -# 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. - -""" -Temporary Compiler passes -""" -from __future__ import annotations -from collections import defaultdict -from functools import singledispatch -from rustworkx import PyDAG - -from qiskit.pulse.model import MixedFrame -from qiskit.pulse.ir import SequenceIR -from qiskit.pulse.ir.ir import InNode, OutNode -from qiskit.pulse.ir.alignments import ( - Alignment, - AlignLeft, - ParallelAlignment, - AlignRight, - SequentialAlignment, - AlignSequential, -) - - -def analyze_target_frame_pass(ir_block: SequenceIR, property_set) -> None: - """Map the dependency of ``MixedFrame``s on ``PulseTarget`` and ``Frame``. - - Recursively traverses through the ``ir_block`` to find all ``MixedFrame``s, - and maps their dependencies on ``PulseTarget`` and ``Frame``. The results - are added as a dictionary to ``property_set`` under the key ``target_frame_map``. - The added dictionary is keyed on every ``PulseTarget`` and ``Frame`` in ``ir_block`` - with the value being a set of all ``MixedFrame``s associated with the key. - """ - target_frame_map = defaultdict(set) - _analyze_target_frame_in_block(ir_block, target_frame_map) - property_set["target_frame_map"] = dict(target_frame_map) - - -def _analyze_target_frame_in_block(ir_block: SequenceIR, target_frame_map: dict) -> None: - """A helper function to recurse through the block while mapping mixed frame dependency""" - for elm in ir_block.elements(): - # Sub Block - if isinstance(elm, SequenceIR): - _analyze_target_frame_in_block(elm, target_frame_map) - # Pulse Instruction - else: - if isinstance(elm.inst_target, MixedFrame): - target_frame_map[elm.inst_target.frame].add(elm.inst_target) - target_frame_map[elm.inst_target.pulse_target].add(elm.inst_target) - - -def sequence_pass(ir_block: SequenceIR, property_set: dict) -> SequenceIR: - """Finalize the sequence of the SequenceIR by adding edges to the DAG""" - _sequence_instructions(ir_block.alignment, ir_block._sequence, property_set) - return ir_block - - -@singledispatch -def _sequence_instructions(alignment: Alignment, sequence: PyDAG, property_set: dict): - """Finalize the sequence of the SequenceIR by adding edges to the DAG""" - raise NotImplementedError - - -@_sequence_instructions.register(ParallelAlignment) -def _sequence_parallel(alignment, sequence, property_set): - """Sequence the SequenceIR by recursively adding edges to the DAG, - adding elements in parallel to one another in the graph""" - idle_after = {} - for ni in sequence.node_indices(): - if ni in (0, 1): - # In, Out node - continue - node = sequence.get_node_data(ni) - node_mixed_frames = set() - - if isinstance(node, SequenceIR): - # Recurse over sub block - sequence_pass(node, property_set) - inst_targets = node.inst_targets - else: - inst_targets = [node.inst_target] - - for inst_target in inst_targets: - if isinstance(inst_target, MixedFrame): - node_mixed_frames.add(inst_target) - else: - node_mixed_frames |= property_set["target_frame_map"][inst_target] - - pred_nodes = [ - idle_after[mixed_frame] - for mixed_frame in node_mixed_frames - if mixed_frame in idle_after - ] - if len(pred_nodes) == 0: - pred_nodes = [0] - for pred_node in pred_nodes: - sequence.add_edge(pred_node, ni, None) - for mixed_frame in node_mixed_frames: - idle_after[mixed_frame] = ni - sequence.add_edges_from_no_data([(ni, 1) for ni in idle_after.values()]) - - -@_sequence_instructions.register(SequentialAlignment) -def _sequence_sequential(alignment, sequence: PyDAG, property_set): - """Sequence the SequenceIR by recursively adding edges to the DAG, - adding elements one after the other""" - sequence.add_edge(0, 2, None) - sequence.add_edge(sequence.num_nodes() - 1, 1, None) - sequence.add_edges_from_no_data([(x, x + 1) for x in range(2, sequence.num_nodes() - 1)]) - - # Apply recursively - for node in sequence.nodes(): - if isinstance(node, SequenceIR): - sequence_pass(node, property_set) - - -def schedule_pass(ir_block: SequenceIR, property_set: dict) -> SequenceIR: - """Recursively schedule the SequenceIR by setting initial time for every element""" - # mutate graph - # TODO : Check if graph has edges. Override existing edges? Raise Error? - _schedule_elements(ir_block.alignment, ir_block._time_table, ir_block._sequence, property_set) - return ir_block - - -@singledispatch -def _schedule_elements(alignment, table, sequence, property_set): - """Recursively schedule the SequenceIR by setting initial time for every element""" - raise NotImplementedError - - -@_schedule_elements.register(AlignLeft) -def _schedule_left_justified(alignment, table: dict, sequence: PyDAG, property_set: dict): - """Recursively schedule the SequenceIR by setting initial time for every element, - aligning elements to the left.""" - - first_nodes = sequence.successor_indices(0) - nodes = [] - # Node 0 has no duration so is treated separately - for node_index in first_nodes: - table[node_index] = 0 - node = sequence.get_node_data(node_index) - if isinstance(node, SequenceIR): - # Recruse over sub blocks - schedule_pass(node, property_set) - nodes.extend(sequence.successor_indices(node_index)) - - while nodes: - node_index = nodes.pop(0) - if node_index == 1 or node_index in table: - # reached end or already scheduled - continue - node = sequence.get_node_data(node_index) - if isinstance(node, SequenceIR): - # Recruse over sub blocks - schedule_pass(node, property_set) - # Because we go in order, all predecessors are already scheduled - preds = sequence.predecessor_indices(node_index) - t0 = max([table.get(pred) + sequence.get_node_data(pred).duration for pred in preds]) - table[node_index] = t0 - nodes.extend(sequence.successor_indices(node_index)) - - -@_schedule_elements.register(AlignSequential) -def _schedule_sequential(alignment, table: dict, sequence: PyDAG, property_set: dict): - """Recursively schedule the block by setting initial time for every element, - assuming the elements are sequential""" - - # TODO: This placeholder will fail if any change is done to the graph, - # needs a more robust implementation. - - total_time = 0 - - for i in range(2, sequence.num_nodes()): - table[i] = total_time - node = sequence.get_node_data(i) - if isinstance(node, SequenceIR): - schedule_pass(node, property_set) - total_time += node.duration - - -@_schedule_elements.register(AlignRight) -def _schedule_right_justified(alignment, table: dict, sequence: PyDAG, property_set: dict) -> None: - """Recursively schedule the block by setting initial time for every element, - aligning elements to the right.""" - - reversed_sequence = sequence.copy() - # Reverse all edge - reversed_sequence.reverse() - # Now swap 0 and 1 nodes - new_start_node_successors = reversed_sequence.successor_indices(1) - new_end_node_predecessors = reversed_sequence.predecessor_indices(0) - reversed_sequence.remove_node(0) - reversed_sequence.remove_node(1) - reversed_sequence.add_node(InNode) - reversed_sequence.add_node(OutNode) - reversed_sequence.add_edges_from_no_data([(0, x) for x in new_start_node_successors]) - reversed_sequence.add_edges_from_no_data([(x, 1) for x in new_end_node_predecessors]) - - # Schedule with left alignment - _schedule_left_justified(alignment, table, reversed_sequence, property_set) - - # Then reverse the timings - total_duration = max( - [ - table[i] + reversed_sequence.get_node_data(i).duration - for i in reversed_sequence.predecessor_indices(1) - ] - ) - for key in table.keys(): - table[key] = total_duration - table[key] - reversed_sequence.get_node_data(key).duration diff --git a/test/python/pulse/test_pulse_compiler_passes.py b/test/python/pulse/test_pulse_compiler_passes.py deleted file mode 100644 index cc651d9e9ccb..000000000000 --- a/test/python/pulse/test_pulse_compiler_passes.py +++ /dev/null @@ -1,885 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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 compiler passes""" -import copy -from test import QiskitTestCase - -from ddt import ddt, named_data, unpack - -from qiskit.pulse import ( - Constant, - Play, - Delay, - ShiftPhase, -) - -from qiskit.pulse.ir import ( - SequenceIR, -) - -from qiskit.pulse.ir.alignments import ( - AlignLeft, - AlignRight, - ParallelAlignment, - SequentialAlignment, - AlignSequential, -) -from qiskit.pulse.model import QubitFrame, Qubit, MixedFrame -from qiskit.pulse.compiler import analyze_target_frame_pass, sequence_pass, schedule_pass - - -class TestAnalyzeTargetFramePass(QiskitTestCase): - """Test analyze_target_frame_pass""" - - def test_basic_ir(self): - """test with basic IR""" - ir_example = SequenceIR(AlignLeft()) - mf = MixedFrame(Qubit(0), QubitFrame(1)) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf)) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - self.assertEqual(len(property_set.keys()), 1) - mapping = property_set["target_frame_map"] - self.assertEqual(len(mapping), 2) - self.assertEqual(mapping[mf.pulse_target], {mf}) - self.assertEqual(mapping[mf.frame], {mf}) - - mf2 = MixedFrame(Qubit(0), QubitFrame(2)) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf2)) - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - mapping = property_set["target_frame_map"] - self.assertEqual(len(mapping), 3) - self.assertEqual(mapping[mf.pulse_target], {mf, mf2}) - self.assertEqual(mapping[mf.frame], {mf}) - - def test_with_several_inst_target_types(self): - """test with different inst_target types""" - ir_example = SequenceIR(AlignLeft()) - mf = MixedFrame(Qubit(0), QubitFrame(1)) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf)) - ir_example.append(Delay(100, target=Qubit(2))) - ir_example.append(ShiftPhase(100, frame=QubitFrame(2))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - mapping = property_set["target_frame_map"] - self.assertEqual(len(mapping), 2) - self.assertEqual(mapping[Qubit(0)], {mf}) - self.assertEqual(mapping[QubitFrame(1)], {mf}) - - def test_with_sub_blocks(self): - """test with sub blocks""" - mf1 = MixedFrame(Qubit(0), QubitFrame(0)) - mf2 = MixedFrame(Qubit(0), QubitFrame(1)) - mf3 = MixedFrame(Qubit(0), QubitFrame(2)) - - sub_block_2 = SequenceIR(AlignLeft()) - sub_block_2.append(Play(Constant(100, 0.1), mixed_frame=mf1)) - - sub_block_1 = SequenceIR(AlignLeft()) - sub_block_1.append(Play(Constant(100, 0.1), mixed_frame=mf2)) - sub_block_1.append(sub_block_2) - - property_set = {} - analyze_target_frame_pass(sub_block_1, property_set) - mapping = property_set["target_frame_map"] - self.assertEqual(len(mapping), 3) - self.assertEqual(mapping[Qubit(0)], {mf1, mf2}) - self.assertEqual(mapping[QubitFrame(0)], {mf1}) - self.assertEqual(mapping[QubitFrame(1)], {mf2}) - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf3)) - ir_example.append(sub_block_1) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - mapping = property_set["target_frame_map"] - self.assertEqual(len(mapping), 4) - self.assertEqual(mapping[Qubit(0)], {mf1, mf2, mf3}) - self.assertEqual(mapping[QubitFrame(0)], {mf1}) - self.assertEqual(mapping[QubitFrame(1)], {mf2}) - self.assertEqual(mapping[QubitFrame(2)], {mf3}) - - -@ddt -class TestSequenceParallelAlignment(QiskitTestCase): - """Test sequence_pass with Parallel Alignment""" - - def test_single_instruction(self): - """test with a single instruction""" - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 2) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((2, 1) in edge_list) - - # TODO: Take care of this weird edge case - # def test_instruction_not_in_mapping(self): - # """test with an instruction which is not in the mapping""" - # - # ir_example = SequenceIR(AlignLeft()) - # ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - # ir_example.append(Delay(100, target=Qubit(5))) - # - # property_set = {} - # analyze_target_frame_pass(ir_example, property_set) - # ir_example = sequence_pass(ir_example, property_set) - # edge_list = ir_example.sequence.edge_list() - # self.assertEqual(len(edge_list), 4) - # self.assertTrue((0, 2) in edge_list) - # self.assertTrue((0, 3) in edge_list) - # self.assertTrue((2, 1) in edge_list) - # self.assertTrue((3, 1) in edge_list) - - def test_parallel_instructions(self): - """test with two parallel instructions""" - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 4) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((2, 1) in edge_list) - self.assertTrue((0, 3) in edge_list) - self.assertTrue((3, 1) in edge_list) - - def test_sequential_instructions(self): - """test with two sequential instructions""" - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 3) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((2, 3) in edge_list) - self.assertTrue((3, 1) in edge_list) - - def test_pulse_target_instruction_broadcasting_to_children(self): - """test with an instruction which is defined on a PulseTarget and is - broadcasted to several children""" - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(Delay(100, target=Qubit(0))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 5) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((2, 3) in edge_list) - self.assertTrue((2, 4) in edge_list) - self.assertTrue((3, 1) in edge_list) - self.assertTrue((4, 1) in edge_list) - - def test_frame_instruction_broadcasting_to_children(self): - """test with an instruction which is defined on a Frame and is broadcasted to several children""" - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 5) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((2, 3) in edge_list) - self.assertTrue((2, 4) in edge_list) - self.assertTrue((3, 1) in edge_list) - self.assertTrue((4, 1) in edge_list) - - def test_pulse_target_instruction_dependency(self): - """test with an instruction which is defined on a PulseTarget and depends on - several mixed frames""" - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) - ir_example.append(Delay(100, target=Qubit(0))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 5) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((0, 3) in edge_list) - self.assertTrue((2, 4) in edge_list) - self.assertTrue((3, 4) in edge_list) - self.assertTrue((4, 1) in edge_list) - - def test_frame_instruction_dependency(self): - """test with an instruction which is defined on a Frame and depends on several mixed frames""" - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) - ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 5) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((0, 3) in edge_list) - self.assertTrue((2, 4) in edge_list) - self.assertTrue((3, 4) in edge_list) - self.assertTrue((4, 1) in edge_list) - - def test_recursion_to_sub_blocks(self): - """test that sequencing is recursively applied to sub blocks""" - - sub_block = SequenceIR(ParallelAlignment()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(sub_block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - - edge_list_sub_block = ir_example.elements()[1].sequence.edge_list() - self.assertEqual(len(edge_list_sub_block), 2) - self.assertTrue((0, 2) in edge_list_sub_block) - self.assertTrue((2, 1) in edge_list_sub_block) - - def test_with_parallel_sub_block(self): - """test with a sub block which doesn't depend on previous instructions""" - - sub_block = SequenceIR(ParallelAlignment()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(sub_block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 4) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((0, 3) in edge_list) - self.assertTrue((2, 1) in edge_list) - self.assertTrue((3, 1) in edge_list) - - def test_with_simple_sequential_sub_block(self): - """test with a sub block which depends on a single previous instruction""" - - sub_block = SequenceIR(ParallelAlignment()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(sub_block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 5) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((0, 3) in edge_list) - self.assertTrue((3, 4) in edge_list) - self.assertTrue((2, 1) in edge_list) - self.assertTrue((4, 1) in edge_list) - - def test_with_sequential_sub_block_with_more_dependencies(self): - """test with a sub block which depends on a single previous instruction""" - - sub_block = SequenceIR(ParallelAlignment()) - sub_block.append(Delay(100, target=Qubit(0))) - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - ir_example.append(sub_block) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 8) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((0, 3) in edge_list) - self.assertTrue((0, 4) in edge_list) - self.assertTrue((2, 5) in edge_list) - self.assertTrue((3, 5) in edge_list) - self.assertTrue((4, 6) in edge_list) - self.assertTrue((5, 1) in edge_list) - self.assertTrue((6, 1) in edge_list) - - @named_data(["align_left", AlignLeft()], ["align_right", AlignRight()]) - @unpack - def test_specific_alignments(self, alignment): - """Test that specific alignments are the same as parallel alignment""" - - sub_block = SequenceIR(ParallelAlignment()) - sub_block.append(Delay(100, target=Qubit(0))) - - ir_example = SequenceIR(ParallelAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - ir_example.append(sub_block) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - - ir_example_specific = copy.deepcopy(ir_example) - ir_example_specific._alignment = alignment - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - - property_set = {} - analyze_target_frame_pass(ir_example_specific, property_set) - ir_example_specific = sequence_pass(ir_example_specific, property_set) - - self.assertEqual(ir_example.sequence.edge_list(), ir_example_specific.sequence.edge_list()) - - -class TestSequenceSequentialAlignment(QiskitTestCase): - """Test sequence_pass with Sequential Alignment""" - - def test_single_instruction(self): - """test with a single instruction""" - - ir_example = SequenceIR(SequentialAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 2) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((2, 1) in edge_list) - - def test_several_instructions(self): - """test with several instructions""" - - ir_example = SequenceIR(SequentialAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(2), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 4) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((2, 3) in edge_list) - self.assertTrue((3, 4) in edge_list) - self.assertTrue((4, 1) in edge_list) - - def test_recursion_to_sub_blocks(self): - """test that sequencing is recursively applied to sub blocks""" - - sub_block = SequenceIR(SequentialAlignment()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(SequentialAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(sub_block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - - edge_list_sub_block = ir_example.elements()[1].sequence.edge_list() - self.assertEqual(len(edge_list_sub_block), 2) - self.assertTrue((0, 2) in edge_list_sub_block) - self.assertTrue((2, 1) in edge_list_sub_block) - - def test_sub_blocks_and_instructions(self): - """test sequencing with a mix of instructions and sub blocks""" - - sub_block = SequenceIR(SequentialAlignment()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(SequentialAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(sub_block) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 4) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((2, 3) in edge_list) - self.assertTrue((3, 4) in edge_list) - self.assertTrue((4, 1) in edge_list) - - def test_align_sequential(self): - """test sequencing with AlignSequential""" - - sub_block = SequenceIR(SequentialAlignment()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(SequentialAlignment()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(sub_block) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) - - ir_example_specific = copy.deepcopy(ir_example) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - - property_set = {} - analyze_target_frame_pass(ir_example_specific, property_set) - ir_example_specific = sequence_pass(ir_example_specific, property_set) - - edge_list = ir_example.sequence.edge_list() - edge_list_specific = ir_example_specific.sequence.edge_list() - self.assertEqual(edge_list, edge_list_specific) - - -class TestSchedulePassAlignLeft(QiskitTestCase): - """Test schedule_pass with align left""" - - def test_single_instruction(self): - """test with a single instruction""" - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 100) - - def test_parallel_instructions(self): - """test with two parallel instructions""" - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 200) - self.assertEqual(ir_example.scheduled_elements()[0][0], 0) - self.assertEqual(ir_example.scheduled_elements()[1][0], 0) - - def test_sequential_instructions(self): - """test with two sequential instructions""" - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 200) - self.assertEqual(ir_example.scheduled_elements()[0][0], 0) - self.assertEqual(ir_example.scheduled_elements()[1][0], 100) - - def test_multiple_children(self): - """test for a graph where one node has several children""" - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(Delay(100, target=Qubit(0))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 200) - self.assertEqual(ir_example.scheduled_elements()[0][0], 0) - self.assertEqual(ir_example.scheduled_elements()[1][0], 100) - self.assertEqual(ir_example.scheduled_elements()[2][0], 100) - - def test_multiple_parents(self): - """test for a graph where one node has several parents""" - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) - ir_example.append(Delay(100, target=Qubit(0))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 300) - self.assertEqual(ir_example.scheduled_elements()[0][0], 0) - self.assertEqual(ir_example.scheduled_elements()[1][0], 0) - self.assertEqual(ir_example.scheduled_elements()[2][0], 200) - - def test_recursion_to_leading_sub_blocks(self): - """test that scheduling is recursively applied to sub blocks which are first in the order""" - - sub_block = SequenceIR(AlignLeft()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(sub_block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - sub_block = ir_example.elements()[1] - # Note that sub blocks are oblivious to their relative timing - self.assertEqual(sub_block.initial_time(), 0) - self.assertEqual(sub_block.final_time(), 100) - self.assertEqual(sub_block.scheduled_elements()[0][0], 0) - - def test_recursion_to_non_leading_sub_blocks(self): - """test that scheduling is recursively applied to sub blocks when they are not first in order""" - - sub_block = SequenceIR(AlignLeft()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(sub_block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - sub_block = ir_example.elements()[2] - # Note that sub blocks are oblivious to their relative timing - self.assertEqual(sub_block.initial_time(), 0) - self.assertEqual(sub_block.final_time(), 100) - self.assertEqual(sub_block.scheduled_elements()[0][0], 0) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 200) - - def test_with_parallel_sub_block(self): - """test with a sub block which doesn't depend on previous instructions""" - - sub_block = SequenceIR(AlignLeft()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(sub_block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 100) - - def test_with_sequential_sub_block_with_more_dependencies(self): - """test with a sub block which depends on a several previous instruction""" - - sub_block = SequenceIR(AlignLeft()) - sub_block.append(Delay(100, target=Qubit(0))) - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - ir_example.append(sub_block) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 300) - self.assertEqual(ir_example.scheduled_elements()[0][0], 0) - self.assertEqual(ir_example.scheduled_elements()[1][0], 0) - self.assertEqual(ir_example.scheduled_elements()[2][0], 0) - self.assertEqual(ir_example.scheduled_elements()[3][0], 200) - self.assertEqual(ir_example.scheduled_elements()[4][0], 100) - - -class TestSchedulePassAlignRight(QiskitTestCase): - """Test schedule_pass with align right""" - - def test_single_instruction(self): - """test with a single instruction""" - - ir_example = SequenceIR(AlignRight()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 100) - - def test_parallel_instructions(self): - """test with two parallel instructions""" - - ir_example = SequenceIR(AlignRight()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 200) - self.assertEqual(ir_example.scheduled_elements()[0][0], 100) - self.assertEqual(ir_example.scheduled_elements()[1][0], 0) - - def test_sequential_instructions(self): - """test with two sequential instructions""" - - ir_example = SequenceIR(AlignRight()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 200) - self.assertEqual(ir_example.scheduled_elements()[0][0], 0) - self.assertEqual(ir_example.scheduled_elements()[1][0], 100) - - def test_multiple_children(self): - """test for a graph where one node has several children""" - - ir_example = SequenceIR(AlignRight()) - ir_example.append(Delay(100, target=Qubit(0))) - ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 300) - self.assertEqual(ir_example.scheduled_elements()[0][0], 0) - self.assertEqual(ir_example.scheduled_elements()[1][0], 100) - self.assertEqual(ir_example.scheduled_elements()[2][0], 200) - - def test_multiple_parents(self): - """test for a graph where one node has several parents""" - - ir_example = SequenceIR(AlignRight()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) - ir_example.append(Delay(100, target=Qubit(0))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 300) - self.assertEqual(ir_example.scheduled_elements()[0][0], 100) - self.assertEqual(ir_example.scheduled_elements()[1][0], 0) - self.assertEqual(ir_example.scheduled_elements()[2][0], 200) - - def test_recursion_to_sub_blocks(self): - """test that scheduling is recursively applied to sub blocks which are first in the order""" - - sub_block = SequenceIR(AlignRight()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(AlignRight()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(sub_block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - sub_block = ir_example.elements()[1] - # Note that sub blocks are oblivious to their relative timing - self.assertEqual(sub_block.initial_time(), 0) - self.assertEqual(sub_block.final_time(), 100) - self.assertEqual(sub_block.scheduled_elements()[0][0], 0) - - def test_with_parallel_sub_block(self): - """test with a sub block which doesn't depend on previous instructions""" - - sub_block = SequenceIR(AlignRight()) - sub_block.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(AlignRight()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(sub_block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 200) - self.assertEqual(ir_example._time_table[2], 100) - self.assertEqual(ir_example._time_table[3], 0) - - def test_with_sequential_sub_block_with_more_dependencies(self): - """test with a sub block which depends on a several previous instruction""" - - sub_block = SequenceIR(AlignRight()) - sub_block.append(Delay(100, target=Qubit(0))) - - ir_example = SequenceIR(AlignRight()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - ir_example.append(sub_block) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 300) - self.assertEqual(ir_example.scheduled_elements()[0][0], 100) - self.assertEqual(ir_example.scheduled_elements()[1][0], 0) - self.assertEqual(ir_example.scheduled_elements()[2][0], 100) - self.assertEqual(ir_example.scheduled_elements()[3][0], 200) - self.assertEqual(ir_example.scheduled_elements()[4][0], 200) - - -class TestSchedulePassAlignSequential(QiskitTestCase): - """Test schedule_pass with align sequential""" - - def test_single_instruction(self): - """test with a single instruction""" - - ir_example = SequenceIR(AlignSequential()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 100) - - def test_several_instructions(self): - """test with several instructions""" - - ir_example = SequenceIR(AlignSequential()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example.append(Play(Constant(200, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 400) - self.assertEqual(ir_example.scheduled_elements()[0][0], 0) - self.assertEqual(ir_example.scheduled_elements()[1][0], 100) - self.assertEqual(ir_example.scheduled_elements()[2][0], 300) - - def test_recursion_to_sub_blocks(self): - """test that scheduling is recursively applied to sub blocks""" - - sub_block = SequenceIR(AlignSequential()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SequenceIR(AlignSequential()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(sub_block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - sub_block = ir_example.elements()[1] - # Note that sub blocks are oblivious to their relative timing - self.assertEqual(sub_block.initial_time(), 0) - self.assertEqual(sub_block.final_time(), 100) - self.assertEqual(sub_block.scheduled_elements()[0][0], 0) - - def test_with_instructions_and_sub_blocks(self): - """test that scheduling is recursively applied to sub blocks""" - - sub_block = SequenceIR(AlignSequential()) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - - ir_example = SequenceIR(AlignSequential()) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - ir_example.append(sub_block) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - self.assertEqual(ir_example.initial_time(), 0) - self.assertEqual(ir_example.final_time(), 400) - self.assertEqual(ir_example.scheduled_elements()[0][0], 0) - self.assertEqual(ir_example.scheduled_elements()[1][0], 100) - self.assertEqual(ir_example.scheduled_elements()[2][0], 300) diff --git a/test/python/pulse/test_pulse_ir.py b/test/python/pulse/test_pulse_ir.py index cacf5658c414..71b84449f3b4 100644 --- a/test/python/pulse/test_pulse_ir.py +++ b/test/python/pulse/test_pulse_ir.py @@ -30,11 +30,6 @@ from qiskit.pulse.ir.alignments import AlignLeft, AlignRight from qiskit.pulse.model import QubitFrame, Qubit -from qiskit.pulse.compiler.temp_passes import ( - sequence_pass, - schedule_pass, - analyze_target_frame_pass, -) from qiskit.pulse.exceptions import PulseError @@ -196,176 +191,177 @@ def test_scheduled_elements_with_recursion_raises_error(self): with self.assertRaises(PulseError): ir_example.scheduled_elements(recursive=True) - def test_flatten_ir_no_sub_blocks(self): - """Test that flattening ir with no sub blocks doesn't do anything""" - ir_example = SequenceIR(AlignLeft()) - inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) - ir_example.append(inst) - ir_example.append(inst2) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - self.assertEqual(ir_example.flatten(), ir_example) - - def test_flatten_inplace_flag(self): - """Test that inplace flag in flattening works""" - ir_example = SequenceIR(AlignLeft()) - inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) - ir_example.append(inst) - ir_example.append(inst2) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - self.assertTrue(ir_example.flatten(inplace=True) is ir_example) - self.assertFalse(ir_example.flatten() is ir_example) - - def test_flatten_one_sub_block(self): - """Test that flattening works with one block""" - ir_example = SequenceIR(AlignLeft()) - inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) - block = SequenceIR(AlignLeft()) - block.append(inst) - block.append(inst2) - ir_example.append(block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - flat = ir_example.flatten() - edge_list = flat.sequence.edge_list() - print(edge_list) - self.assertEqual(len(edge_list), 4) - self.assertTrue((0, 5) in edge_list) - self.assertTrue((0, 6) in edge_list) - self.assertTrue((5, 1) in edge_list) - self.assertTrue((6, 1) in edge_list) - - def test_flatten_one_sub_block_and_parallel_instruction(self): - """Test that flattening works with one block and parallel instruction""" - ir_example = SequenceIR(AlignLeft()) - inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) - block = SequenceIR(AlignLeft()) - block.append(inst) - block.append(inst2) - ir_example.append(block) - ir_example.append(Play(Constant(100, 0.5), frame=QubitFrame(3), target=Qubit(3))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - flat = ir_example.flatten() - edge_list = flat.sequence.edge_list() - self.assertEqual(len(edge_list), 6) - self.assertTrue((0, 3) in edge_list) - self.assertTrue((3, 1) in edge_list) - self.assertTrue((0, 6) in edge_list) - self.assertTrue((0, 7) in edge_list) - self.assertTrue((6, 1) in edge_list) - self.assertTrue((7, 1) in edge_list) - - def test_flatten_one_sub_block_and_sequential_instructions(self): - """Test that flattening works with one block and sequential instructions""" - ir_example = SequenceIR(AlignLeft()) - inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) - block = SequenceIR(AlignLeft()) - block.append(inst) - block.append(inst2) - ir_example.append(Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1))) - ir_example.append(block) - ir_example.append(Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2))) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - flat = ir_example.flatten() - edge_list = flat.sequence.edge_list() - self.assertEqual(len(edge_list), 8) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((4, 1) in edge_list) - self.assertTrue((2, 7) in edge_list) - self.assertTrue((2, 8) in edge_list) - self.assertTrue((7, 1) in edge_list) - self.assertTrue((8, 1) in edge_list) - self.assertTrue((7, 4) in edge_list) - self.assertTrue((8, 4) in edge_list) - - def test_flatten_two_sub_blocks(self): - """Test that flattening works with two sub blocks""" - inst1 = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - inst2 = Play(Constant(200, 0.5), frame=QubitFrame(1), target=Qubit(1)) - - block1 = SequenceIR(AlignLeft()) - block1.append(inst1) - block2 = SequenceIR(AlignLeft()) - block2.append(inst2) - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(block1) - ir_example.append(block2) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - flat = ir_example.flatten() - ref = SequenceIR(AlignLeft()) - ref.append(inst1) - ref.append(inst2) - - property_set = {} - analyze_target_frame_pass(ref, property_set) - ref = sequence_pass(ref, property_set) - ref = schedule_pass(ref, property_set) - - self.assertEqual(flat, ref) - - def test_flatten_two_levels(self): - """Test that flattening works with one block and sequential instructions""" - inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - - block1 = SequenceIR(AlignLeft()) - block1.append(inst) - block = SequenceIR(AlignLeft()) - block.append(inst) - block.append(block1) - - ir_example = SequenceIR(AlignLeft()) - ir_example.append(inst) - ir_example.append(block) - - property_set = {} - analyze_target_frame_pass(ir_example, property_set) - ir_example = sequence_pass(ir_example, property_set) - ir_example = schedule_pass(ir_example, property_set) - - flat = ir_example.flatten() - edge_list = flat.sequence.edge_list() - self.assertEqual(len(edge_list), 4) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((2, 6) in edge_list) - self.assertTrue((6, 7) in edge_list) - self.assertTrue((7, 1) in edge_list) - self.assertEqual(flat.scheduled_elements()[0], (0, inst)) - self.assertEqual(flat.scheduled_elements()[1], (100, inst)) - self.assertEqual(flat.scheduled_elements()[2], (200, inst)) + # TODO : Replace tests which relied on temporary passes with proper passes. + # def test_flatten_ir_no_sub_blocks(self): + # """Test that flattening ir with no sub blocks doesn't do anything""" + # ir_example = SequenceIR(AlignLeft()) + # inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + # inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + # ir_example.append(inst) + # ir_example.append(inst2) + # + # property_set = {} + # analyze_target_frame_pass(ir_example, property_set) + # ir_example = sequence_pass(ir_example, property_set) + # ir_example = schedule_pass(ir_example, property_set) + # + # self.assertEqual(ir_example.flatten(), ir_example) + # + # def test_flatten_inplace_flag(self): + # """Test that inplace flag in flattening works""" + # ir_example = SequenceIR(AlignLeft()) + # inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + # inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + # ir_example.append(inst) + # ir_example.append(inst2) + # + # property_set = {} + # analyze_target_frame_pass(ir_example, property_set) + # ir_example = sequence_pass(ir_example, property_set) + # ir_example = schedule_pass(ir_example, property_set) + # + # self.assertTrue(ir_example.flatten(inplace=True) is ir_example) + # self.assertFalse(ir_example.flatten() is ir_example) + # + # def test_flatten_one_sub_block(self): + # """Test that flattening works with one block""" + # ir_example = SequenceIR(AlignLeft()) + # inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + # inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + # block = SequenceIR(AlignLeft()) + # block.append(inst) + # block.append(inst2) + # ir_example.append(block) + # + # property_set = {} + # analyze_target_frame_pass(ir_example, property_set) + # ir_example = sequence_pass(ir_example, property_set) + # ir_example = schedule_pass(ir_example, property_set) + # + # flat = ir_example.flatten() + # edge_list = flat.sequence.edge_list() + # print(edge_list) + # self.assertEqual(len(edge_list), 4) + # self.assertTrue((0, 5) in edge_list) + # self.assertTrue((0, 6) in edge_list) + # self.assertTrue((5, 1) in edge_list) + # self.assertTrue((6, 1) in edge_list) + # + # def test_flatten_one_sub_block_and_parallel_instruction(self): + # """Test that flattening works with one block and parallel instruction""" + # ir_example = SequenceIR(AlignLeft()) + # inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + # inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + # block = SequenceIR(AlignLeft()) + # block.append(inst) + # block.append(inst2) + # ir_example.append(block) + # ir_example.append(Play(Constant(100, 0.5), frame=QubitFrame(3), target=Qubit(3))) + # + # property_set = {} + # analyze_target_frame_pass(ir_example, property_set) + # ir_example = sequence_pass(ir_example, property_set) + # ir_example = schedule_pass(ir_example, property_set) + # + # flat = ir_example.flatten() + # edge_list = flat.sequence.edge_list() + # self.assertEqual(len(edge_list), 6) + # self.assertTrue((0, 3) in edge_list) + # self.assertTrue((3, 1) in edge_list) + # self.assertTrue((0, 6) in edge_list) + # self.assertTrue((0, 7) in edge_list) + # self.assertTrue((6, 1) in edge_list) + # self.assertTrue((7, 1) in edge_list) + # + # def test_flatten_one_sub_block_and_sequential_instructions(self): + # """Test that flattening works with one block and sequential instructions""" + # ir_example = SequenceIR(AlignLeft()) + # inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + # inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + # block = SequenceIR(AlignLeft()) + # block.append(inst) + # block.append(inst2) + # ir_example.append(Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1))) + # ir_example.append(block) + # ir_example.append(Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2))) + # + # property_set = {} + # analyze_target_frame_pass(ir_example, property_set) + # ir_example = sequence_pass(ir_example, property_set) + # ir_example = schedule_pass(ir_example, property_set) + # + # flat = ir_example.flatten() + # edge_list = flat.sequence.edge_list() + # self.assertEqual(len(edge_list), 8) + # self.assertTrue((0, 2) in edge_list) + # self.assertTrue((4, 1) in edge_list) + # self.assertTrue((2, 7) in edge_list) + # self.assertTrue((2, 8) in edge_list) + # self.assertTrue((7, 1) in edge_list) + # self.assertTrue((8, 1) in edge_list) + # self.assertTrue((7, 4) in edge_list) + # self.assertTrue((8, 4) in edge_list) + # + # def test_flatten_two_sub_blocks(self): + # """Test that flattening works with two sub blocks""" + # inst1 = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + # inst2 = Play(Constant(200, 0.5), frame=QubitFrame(1), target=Qubit(1)) + # + # block1 = SequenceIR(AlignLeft()) + # block1.append(inst1) + # block2 = SequenceIR(AlignLeft()) + # block2.append(inst2) + # + # ir_example = SequenceIR(AlignLeft()) + # ir_example.append(block1) + # ir_example.append(block2) + # + # property_set = {} + # analyze_target_frame_pass(ir_example, property_set) + # ir_example = sequence_pass(ir_example, property_set) + # ir_example = schedule_pass(ir_example, property_set) + # + # flat = ir_example.flatten() + # ref = SequenceIR(AlignLeft()) + # ref.append(inst1) + # ref.append(inst2) + # + # property_set = {} + # analyze_target_frame_pass(ref, property_set) + # ref = sequence_pass(ref, property_set) + # ref = schedule_pass(ref, property_set) + # + # self.assertEqual(flat, ref) + # + # def test_flatten_two_levels(self): + # """Test that flattening works with one block and sequential instructions""" + # inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + # + # block1 = SequenceIR(AlignLeft()) + # block1.append(inst) + # block = SequenceIR(AlignLeft()) + # block.append(inst) + # block.append(block1) + # + # ir_example = SequenceIR(AlignLeft()) + # ir_example.append(inst) + # ir_example.append(block) + # + # property_set = {} + # analyze_target_frame_pass(ir_example, property_set) + # ir_example = sequence_pass(ir_example, property_set) + # ir_example = schedule_pass(ir_example, property_set) + # + # flat = ir_example.flatten() + # edge_list = flat.sequence.edge_list() + # self.assertEqual(len(edge_list), 4) + # self.assertTrue((0, 2) in edge_list) + # self.assertTrue((2, 6) in edge_list) + # self.assertTrue((6, 7) in edge_list) + # self.assertTrue((7, 1) in edge_list) + # self.assertEqual(flat.scheduled_elements()[0], (0, inst)) + # self.assertEqual(flat.scheduled_elements()[1], (100, inst)) + # self.assertEqual(flat.scheduled_elements()[2], (200, inst)) def test_ir_equating_different_alignment(self): """Test equating of blocks with different alignment""" @@ -382,31 +378,31 @@ def test_ir_equating_different_instructions(self): ir2.append(inst2) self.assertFalse(ir1 == ir2) - def test_ir_equating_different_ordering(self): - """Test equating of blocks with different ordering, but the same sequence structure""" - inst1 = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) - - ir1 = SequenceIR(AlignLeft()) - ir1.append(inst1) - ir1.append(inst2) - - ir2 = SequenceIR(AlignLeft()) - ir2.append(inst2) - ir2.append(inst1) - - self.assertTrue(ir1 == ir2) - - property_set = {} - analyze_target_frame_pass(ir1, property_set) - ir1 = sequence_pass(ir1, property_set) - ir1 = schedule_pass(ir1, property_set) - - property_set = {} - analyze_target_frame_pass(ir2, property_set) - ir2 = sequence_pass(ir2, property_set) - ir2 = schedule_pass(ir2, property_set) - - self.assertTrue(ir1 == ir2) + # def test_ir_equating_different_ordering(self): + # """Test equating of blocks with different ordering, but the same sequence structure""" + # inst1 = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + # inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + # + # ir1 = SequenceIR(AlignLeft()) + # ir1.append(inst1) + # ir1.append(inst2) + # + # ir2 = SequenceIR(AlignLeft()) + # ir2.append(inst2) + # ir2.append(inst1) + # + # self.assertTrue(ir1 == ir2) + # + # property_set = {} + # analyze_target_frame_pass(ir1, property_set) + # ir1 = sequence_pass(ir1, property_set) + # ir1 = schedule_pass(ir1, property_set) + # + # property_set = {} + # analyze_target_frame_pass(ir2, property_set) + # ir2 = sequence_pass(ir2, property_set) + # ir2 = schedule_pass(ir2, property_set) + # + # self.assertTrue(ir1 == ir2) # TODO : Test SequenceIR.draw() From 52158f6472f64820d852bc029c0f4310a03b2077 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:41:32 +0200 Subject: [PATCH 06/15] Update qiskit/pulse/ir/ir.py Co-authored-by: Naoki Kanazawa --- qiskit/pulse/ir/ir.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/pulse/ir/ir.py b/qiskit/pulse/ir/ir.py index 75d3c23d74ed..e2c6c0408f83 100644 --- a/qiskit/pulse/ir/ir.py +++ b/qiskit/pulse/ir/ir.py @@ -197,8 +197,7 @@ def edge_map(x, y, node): return None for ind in block.sequence.node_indices(): - if isinstance(block.sequence.get_node_data(ind), SequenceIR): - sub_block = block.sequence.get_node_data(ind) + if isinstance(sub_block := block.sequence.get_node_data(ind), SequenceIR): sub_block.flatten(inplace=True) initial_time = block._time_table[ind] nodes_mapping = block._sequence.substitute_node_with_subgraph( From 1f0e13c2edb6eb9a1a152221085222448f740b52 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:30:12 +0200 Subject: [PATCH 07/15] Corrections. --- qiskit/pulse/compiler/basepasses.py | 8 +- qiskit/pulse/compiler/passmanager.py | 20 ++--- qiskit/pulse/ir/alignments.py | 52 ------------ qiskit/pulse/ir/ir.py | 90 ++++++++++++++------ test/python/pulse/test_pulse_ir.py | 119 ++++++++++++++++++--------- 5 files changed, 162 insertions(+), 127 deletions(-) delete mode 100644 qiskit/pulse/ir/alignments.py diff --git a/qiskit/pulse/compiler/basepasses.py b/qiskit/pulse/compiler/basepasses.py index 8e1c40443ae7..284e8e8f5946 100644 --- a/qiskit/pulse/compiler/basepasses.py +++ b/qiskit/pulse/compiler/basepasses.py @@ -17,7 +17,7 @@ from qiskit.passmanager.base_tasks import GenericPass from qiskit.transpiler.target import Target -from qiskit.pulse.ir import IrBlock +from qiskit.pulse.ir import SequenceIR class TransformationPass(GenericPass, ABC): @@ -42,8 +42,8 @@ def __init__( @abstractmethod def run( self, - passmanager_ir: IrBlock, - ) -> IrBlock: + passmanager_ir: SequenceIR, + ) -> SequenceIR: pass def __eq__(self, other): @@ -79,7 +79,7 @@ def __init__( @abstractmethod def run( self, - passmanager_ir: IrBlock, + passmanager_ir: SequenceIR, ) -> None: pass diff --git a/qiskit/pulse/compiler/passmanager.py b/qiskit/pulse/compiler/passmanager.py index b86f36963dd9..97e1ca58de16 100644 --- a/qiskit/pulse/compiler/passmanager.py +++ b/qiskit/pulse/compiler/passmanager.py @@ -19,7 +19,7 @@ from typing import Any from qiskit.passmanager import BasePassManager -from qiskit.pulse.ir import IrBlock, IrInstruction +from qiskit.pulse.ir import SequenceIR from qiskit.pulse.schedule import ScheduleBlock @@ -52,14 +52,14 @@ def _passmanager_frontend( self, input_program: ScheduleBlock, **kwargs, - ) -> IrBlock: + ) -> SequenceIR: def _wrap_recursive(_prog): - _ret = IrBlock(alignment=input_program.alignment_context) + _ret = SequenceIR(alignment=input_program.alignment_context) for _elm in _prog.blocks: if isinstance(_elm, ScheduleBlock): return _wrap_recursive(_elm) - _ret.add_element(IrInstruction(instruction=_elm)) + _ret.append(_elm) return _ret return _wrap_recursive(input_program) @@ -120,16 +120,16 @@ def callback_func(**kwargs): class BlockToIrCompiler(BasePulsePassManager): """A specialized pulse compiler for IR backend. - This compiler outputs :class:`.IrBlock`, which is an intermediate representation + This compiler outputs :class:`.SequenceIR`, which is an intermediate representation of the pulse program in Qiskit. """ def _passmanager_backend( self, - passmanager_ir: IrBlock, + passmanager_ir: SequenceIR, in_program: ScheduleBlock, **kwargs, - ) -> IrBlock: + ) -> SequenceIR: return passmanager_ir @@ -142,15 +142,15 @@ class BlockTranspiler(BasePulsePassManager): def _passmanager_backend( self, - passmanager_ir: IrBlock, + passmanager_ir: SequenceIR, in_program: ScheduleBlock, **kwargs, ) -> ScheduleBlock: def _unwrap_recursive(_prog): - _ret = ScheduleBlock(alignment_context=passmanager_ir.alignment) + _ret = ScheduleBlock(alignment_context=_prog.alignment) for _elm in _prog.elements: - if isinstance(_elm, IrBlock): + if isinstance(_elm, SequenceIR): return _unwrap_recursive(_elm) _ret.append(_elm.instruction, inplace=True) return _ret diff --git a/qiskit/pulse/ir/alignments.py b/qiskit/pulse/ir/alignments.py deleted file mode 100644 index 53b5f7cc7b87..000000000000 --- a/qiskit/pulse/ir/alignments.py +++ /dev/null @@ -1,52 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2024. -# -# 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. -""" -========= -Pulse IR Alignments -========= -""" - - -class Alignment: - """Base abstract class for IR alignments""" - - pass - - -class ParallelAlignment(Alignment): - """Base abstract class for IR alignments which align instructions in parallel""" - - pass - - -class SequentialAlignment(Alignment): - """Base abstract class for IR alignments which align instructions sequentially""" - - pass - - -class AlignLeft(ParallelAlignment): - """Left Alignment""" - - pass - - -class AlignRight(ParallelAlignment): - """Right Alignment""" - - pass - - -class AlignSequential(SequentialAlignment): - """Sequential Alignment""" - - pass diff --git a/qiskit/pulse/ir/ir.py b/qiskit/pulse/ir/ir.py index e2c6c0408f83..08c0a11c22ca 100644 --- a/qiskit/pulse/ir/ir.py +++ b/qiskit/pulse/ir/ir.py @@ -27,12 +27,10 @@ from rustworkx import PyDAG from rustworkx.visualization import graphviz_draw -from qiskit.pulse.ir.alignments import Alignment +from qiskit.pulse.transforms import AlignmentKind from qiskit.pulse import Instruction from qiskit.pulse.exceptions import PulseError - -InNode = object() -OutNode = object() +from qiskit.pulse.model import PulseTarget, Frame, MixedFrame class SequenceIR: @@ -43,20 +41,28 @@ class SequenceIR: which include ``IrInstruction`` objects and other nested ``SequenceIR`` objects. """ - def __init__(self, alignment: Alignment): + _InNode = object() + _OutNode = object() + + def __init__(self, alignment: AlignmentKind): + """Create new ``SequenceIR`` + + Args: + alignment: The alignment of the object. + """ self._alignment = alignment self._time_table = defaultdict(lambda: None) self._sequence = rx.PyDAG(multigraph=False) - self._sequence.add_nodes_from([InNode, OutNode]) + self._sequence.add_nodes_from([SequenceIR._InNode, SequenceIR._OutNode]) @property - def alignment(self) -> Alignment: + def alignment(self) -> AlignmentKind: """Return the alignment of the SequenceIR""" return self._alignment @property - def inst_targets(self) -> set: + def inst_targets(self) -> set[PulseTarget | Frame | MixedFrame]: """Recursively return a set of all Instruction.inst_target in the SequenceIR""" inst_targets = set() for elm in self.elements(): @@ -74,7 +80,11 @@ def sequence(self) -> PyDAG: def append(self, element: SequenceIR | Instruction) -> int: """Append element to the SequenceIR - Returns: The index of the added element in the sequence. + Args: + element: The element to be added, either a pulse ``Instruction`` or ``SequenceIR``. + + Returns: + The index of the added element in the sequence. """ return self._sequence.add_node(element) @@ -87,17 +97,28 @@ def scheduled_elements( ) -> list[tuple[int | None, SequenceIR | Instruction]]: """Return a list of scheduled elements. - Each element in the list is [initial_time, element]. + Args: + recursive: Boolean flag. If ``False``, returns only immediate children of the object + (even if they are of type ``SequenceIR``). If ``True``, recursively returns only + instructions, if all children ``SequenceIR`` are scheduled. Defaults to ``False``. + + Returns: + A list of tuples of the form (initial_time, element). if all elements are scheduled, + the returned list will be sorted in ascending order, according to initial time. """ if recursive: - return self._recursive_scheduled_elements(0) + listed_elements = self._recursive_scheduled_elements(0) else: - return [ + listed_elements = [ (self._time_table[ind], self._sequence.get_node_data(ind)) for ind in self._sequence.node_indices() if ind not in (0, 1) ] + if all(x[0] is not None for x in listed_elements): + listed_elements.sort(key=lambda x: x[0]) + return listed_elements + def _recursive_scheduled_elements( self, time_offset: int ) -> list[tuple[int | None, SequenceIR | Instruction]]: @@ -106,7 +127,11 @@ def _recursive_scheduled_elements( The absolute timing is tracked via the `time_offset`` argument, which represents the initial time of the block itself. - Each element in the list is a tuple (initial_time, element). + Args: + time_offset: The initial time of the ``SequenceIR`` object itself. + + Raises: + PulseError: If children ``SequenceIR`` objects are not scheduled. """ scheduled_elements = [] for ind in self._sequence.node_indices(): @@ -128,14 +153,20 @@ def _recursive_scheduled_elements( return scheduled_elements def initial_time(self) -> int | None: - """Return initial time""" + """Return initial time. + + Defaults to ``None``. + """ first_nodes = self._sequence.successor_indices(0) if not first_nodes: return None return min([self._time_table[ind] for ind in first_nodes], default=None) def final_time(self) -> int | None: - """Return final time""" + """Return final time. + + Defaults to ``None``. + """ last_nodes = self._sequence.predecessor_indices(1) if not last_nodes: return None @@ -151,7 +182,10 @@ def final_time(self) -> int | None: @property def duration(self) -> int | None: - """Return the duration of the SequenceIR""" + """Return the duration of the SequenceIR. + + Defaults to ``None``. + """ try: return self.final_time() - self.initial_time() except TypeError: @@ -165,7 +199,7 @@ def draw(self, recursive: bool = False): draw_sequence = self.sequence def _draw_nodes(n): - if n is InNode or n is OutNode: + if n is SequenceIR._InNode or n is SequenceIR._OutNode: return {"fillcolor": "grey", "style": "filled"} try: name = " " + n.name @@ -179,15 +213,21 @@ def _draw_nodes(n): ) def flatten(self, inplace: bool = False) -> SequenceIR: - """Recursively flatten the SequenceIR""" + """Recursively flatten the SequenceIR. + + Args: + inplace: If ``True`` flatten the object itself. If ``False`` return a flattened copy. + + Returns: + A flattened ``SequenceIR`` object. + """ # TODO : Verify that the block\sub blocks are sequenced correctly. if inplace: block = self else: block = copy.deepcopy(self) - block._sequence[0] = InNode - block._sequence[1] = OutNode - # TODO : Move this to __deepcopy__ + block._sequence[0] = SequenceIR._InNode + block._sequence[1] = SequenceIR._OutNode def edge_map(x, y, node): if y == node: @@ -209,19 +249,21 @@ def edge_map(x, y, node): block._time_table[nodes_mapping[old_node]] = ( initial_time + sub_block._time_table[old_node] ) + + del block._time_table[ind] block._sequence.remove_node_retain_edges(nodes_mapping[0]) block._sequence.remove_node_retain_edges(nodes_mapping[1]) return block def __eq__(self, other: SequenceIR): - if not isinstance(other.alignment, type(self.alignment)): + if other.alignment != self.alignment: return False - # TODO : This is a temporary setup until we figure out the - # alignment hierarchy and set equating there. return rx.is_isomorphic_node_match(self._sequence, other._sequence, lambda x, y: x == y) # TODO : What about the time_table? The isomorphic comparison allows for the indices # to be different, But then it's not straightforward to compare the time_table. # It is reasonable to assume that blocks with the same alignment and the same sequence # will result in the same time_table, but this decision should be made consciously. + + # TODO : __repr__ diff --git a/test/python/pulse/test_pulse_ir.py b/test/python/pulse/test_pulse_ir.py index 71b84449f3b4..d667ad58ab59 100644 --- a/test/python/pulse/test_pulse_ir.py +++ b/test/python/pulse/test_pulse_ir.py @@ -15,32 +15,23 @@ from test import QiskitTestCase from qiskit.pulse import ( Constant, - MemorySlot, Play, Delay, - Acquire, ShiftPhase, - DriveChannel, - AcquireChannel, ) from qiskit.pulse.ir import ( SequenceIR, ) -from qiskit.pulse.ir.alignments import AlignLeft, AlignRight -from qiskit.pulse.model import QubitFrame, Qubit +from qiskit.pulse.transforms import AlignLeft, AlignRight +from qiskit.pulse.model import QubitFrame, Qubit, MixedFrame from qiskit.pulse.exceptions import PulseError class TestSequenceIR(QiskitTestCase): """Test SequenceIR objects""" - _delay_inst = Delay(50, channel=DriveChannel(0)) - _play_inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - _shift_phase_inst = ShiftPhase(0.1, channel=DriveChannel(3)) - _acquire_inst = Acquire(200, channel=AcquireChannel(2), mem_slot=MemorySlot(4)) - def test_ir_creation(self): """Test ir creation""" ir_example = SequenceIR(AlignLeft()) @@ -137,6 +128,58 @@ def test_duration_with_sub_block(self): self.assertEqual(ir_example.final_time(), 400) self.assertEqual(ir_example.duration, 300) + def test_inst_targets_no_sub_blocks(self): + """Test that inst targets are recovered correctly with no sub blocks""" + ir_example = SequenceIR(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), frame=QubitFrame(1), target=Qubit(1))) + ir_example.append(Play(Constant(100, 0.1), frame=QubitFrame(2), target=Qubit(2))) + ir_example.append(Delay(100, target=Qubit(3))) + ir_example.append(ShiftPhase(100, frame=QubitFrame(3))) + + # TODO : Make sure this also works for acquire. + + inst_targets = ir_example.inst_targets + ref = { + MixedFrame(Qubit(1), QubitFrame(1)), + MixedFrame(Qubit(2), QubitFrame(2)), + Qubit(3), + QubitFrame(3), + } + + self.assertEqual(inst_targets, ref) + + def test_inst_targets_with_sub_blocks(self): + """Test that inst targets are recovered correctly with sub blocks""" + ir_example = SequenceIR(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), frame=QubitFrame(1), target=Qubit(1))) + + sub_block = SequenceIR(AlignLeft()) + sub_block.append(Delay(100, target=Qubit(1))) + ir_example.append(sub_block) + + inst_targets = ir_example.inst_targets + ref = {MixedFrame(Qubit(1), QubitFrame(1)), Qubit(1)} + + self.assertEqual(inst_targets, ref) + + def test_scheduled_elements_sort(self): + """Test that scheduled elements is sorted correctly""" + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + ir_example = SequenceIR(AlignRight()) + ir_example.append(inst) + ir_example.append(inst) + ir_example.append(inst) + + ir_example._time_table[2] = 200 + ir_example._time_table[3] = 100 + ir_example._time_table[4] = 500 + + sch_elements = ir_example.scheduled_elements() + self.assertEqual(len(sch_elements), 3) + self.assertEqual(sch_elements[0], (100, inst)) + self.assertEqual(sch_elements[1], (200, inst)) + self.assertEqual(sch_elements[2], (500, inst)) + def test_scheduled_elements_no_recursion(self): """Test that scheduled elements with no recursion works""" inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) @@ -362,6 +405,8 @@ def test_scheduled_elements_with_recursion_raises_error(self): # self.assertEqual(flat.scheduled_elements()[0], (0, inst)) # self.assertEqual(flat.scheduled_elements()[1], (100, inst)) # self.assertEqual(flat.scheduled_elements()[2], (200, inst)) + # # Verify that nodes removed from the graph are also removed from _time_table. + # self.assertEqual({x for x in flat.sequence.node_indices()}, flat._time_table.keys()) def test_ir_equating_different_alignment(self): """Test equating of blocks with different alignment""" @@ -378,31 +423,31 @@ def test_ir_equating_different_instructions(self): ir2.append(inst2) self.assertFalse(ir1 == ir2) - # def test_ir_equating_different_ordering(self): - # """Test equating of blocks with different ordering, but the same sequence structure""" - # inst1 = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) - # inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) - # - # ir1 = SequenceIR(AlignLeft()) - # ir1.append(inst1) - # ir1.append(inst2) - # - # ir2 = SequenceIR(AlignLeft()) - # ir2.append(inst2) - # ir2.append(inst1) - # - # self.assertTrue(ir1 == ir2) - # - # property_set = {} - # analyze_target_frame_pass(ir1, property_set) - # ir1 = sequence_pass(ir1, property_set) - # ir1 = schedule_pass(ir1, property_set) - # - # property_set = {} - # analyze_target_frame_pass(ir2, property_set) - # ir2 = sequence_pass(ir2, property_set) - # ir2 = schedule_pass(ir2, property_set) - # - # self.assertTrue(ir1 == ir2) + def test_ir_equating_different_ordering(self): + """Test equating of blocks with different ordering, but the same sequence structure""" + inst1 = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + inst2 = Play(Constant(100, 0.5), frame=QubitFrame(2), target=Qubit(2)) + + ir1 = SequenceIR(AlignLeft()) + ir1.append(inst1) + ir1.append(inst2) + + ir2 = SequenceIR(AlignLeft()) + ir2.append(inst2) + ir2.append(inst1) + + self.assertTrue(ir1 == ir2) + + ir1.sequence.add_edge(0, 2, None) + ir1.sequence.add_edge(0, 3, None) + ir1.sequence.add_edge(3, 1, None) + ir1.sequence.add_edge(2, 1, None) + + ir2.sequence.add_edge(0, 2, None) + ir2.sequence.add_edge(0, 3, None) + ir2.sequence.add_edge(3, 1, None) + ir2.sequence.add_edge(2, 1, None) + + self.assertTrue(ir1 == ir2) # TODO : Test SequenceIR.draw() From 8c19fbf2d7e14862eafd1371980d3f5785591bf8 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:43:03 +0200 Subject: [PATCH 08/15] Add to do. --- qiskit/pulse/ir/ir.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/pulse/ir/ir.py b/qiskit/pulse/ir/ir.py index 08c0a11c22ca..da6bedbc0c52 100644 --- a/qiskit/pulse/ir/ir.py +++ b/qiskit/pulse/ir/ir.py @@ -229,6 +229,8 @@ def flatten(self, inplace: bool = False) -> SequenceIR: block._sequence[0] = SequenceIR._InNode block._sequence[1] = SequenceIR._OutNode + # TODO : Create a dedicated half shallow copier. + def edge_map(x, y, node): if y == node: return 0 From 21dc703ff6799674ed995d78dd86471e21c24148 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Thu, 7 Mar 2024 23:49:55 +0200 Subject: [PATCH 09/15] Fixes --- qiskit/pulse/compiler/passmanager.py | 6 +++--- qiskit/pulse/ir/ir.py | 27 ++++++++++++++------------- test/python/pulse/test_compile.py | 4 ++-- test/python/pulse/test_pulse_ir.py | 24 ++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/qiskit/pulse/compiler/passmanager.py b/qiskit/pulse/compiler/passmanager.py index 8ab419fdfae9..dc8773d3f4bb 100644 --- a/qiskit/pulse/compiler/passmanager.py +++ b/qiskit/pulse/compiler/passmanager.py @@ -58,9 +58,9 @@ def _wrap_recursive(_prog): _ret = SequenceIR(alignment=_prog.alignment_context) for _elm in _prog.blocks: if isinstance(_elm, ScheduleBlock): - _ret.add_element(_wrap_recursive(_elm)) + _ret.append(_wrap_recursive(_elm)) else: - _ret.add_element(_elm) + _ret.append(_elm) return _ret return _wrap_recursive(input_program) @@ -150,7 +150,7 @@ def _passmanager_backend( def _unwrap_recursive(_prog): _ret = ScheduleBlock(alignment_context=_prog.alignment) - for _elm in _prog.elements: + for _elm in _prog.elements(): if isinstance(_elm, SequenceIR): _ret.append(_unwrap_recursive(_elm), inplace=True) else: diff --git a/qiskit/pulse/ir/ir.py b/qiskit/pulse/ir/ir.py index da6bedbc0c52..d35f56da0826 100644 --- a/qiskit/pulse/ir/ir.py +++ b/qiskit/pulse/ir/ir.py @@ -160,7 +160,10 @@ def initial_time(self) -> int | None: first_nodes = self._sequence.successor_indices(0) if not first_nodes: return None - return min([self._time_table[ind] for ind in first_nodes], default=None) + try: + return min(self._time_table[ind] for ind in first_nodes) + except TypeError: + return None def final_time(self) -> int | None: """Return final time. @@ -170,15 +173,13 @@ def final_time(self) -> int | None: last_nodes = self._sequence.predecessor_indices(1) if not last_nodes: return None - tf = None - for ind in last_nodes: - if (t0 := self._time_table[ind]) is not None: - duration = self._sequence.get_node_data(ind).duration - if tf is None: - tf = t0 + duration - else: - tf = max(tf, t0 + duration) - return tf + try: + return max( + self._time_table[ind] + self._sequence.get_node_data(ind).duration + for ind in last_nodes + ) + except TypeError: + return None @property def duration(self) -> int | None: @@ -231,10 +232,10 @@ def flatten(self, inplace: bool = False) -> SequenceIR: # TODO : Create a dedicated half shallow copier. - def edge_map(x, y, node): - if y == node: + def edge_map(_x, _y, _node): + if _y == _node: return 0 - if x == node: + if _x == _node: return 1 return None diff --git a/test/python/pulse/test_compile.py b/test/python/pulse/test_compile.py index c0464e1f03ca..d789c37fb7ac 100644 --- a/test/python/pulse/test_compile.py +++ b/test/python/pulse/test_compile.py @@ -14,7 +14,7 @@ from qiskit.pulse.compiler import BlockTranspiler from qiskit.pulse.compiler.basepasses import TransformationPass -from qiskit.pulse.ir.ir import IrBlock +from qiskit.pulse.ir.ir import SequenceIR from qiskit.providers.fake_provider import GenericBackendV2 from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -24,7 +24,7 @@ class _DummyPass(TransformationPass): """A test pass that doesn't perform any transformation.""" - def run(self, passmanager_ir: IrBlock) -> IrBlock: + def run(self, passmanager_ir: SequenceIR) -> SequenceIR: return passmanager_ir def __hash__(self) -> int: diff --git a/test/python/pulse/test_pulse_ir.py b/test/python/pulse/test_pulse_ir.py index d667ad58ab59..2a09be35714b 100644 --- a/test/python/pulse/test_pulse_ir.py +++ b/test/python/pulse/test_pulse_ir.py @@ -73,6 +73,18 @@ def test_initial_time(self): ir_example._time_table[3] = 0 self.assertEqual(ir_example.initial_time(), 0) + def test_initial_time_partial_scheduling(self): + """Test initial time with partial scheduling""" + ir_example = SequenceIR(AlignLeft()) + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + ir_example.append(inst) + ir_example.append(inst) + ir_example._time_table[2] = 100 + ir_example._time_table[3] = None + ir_example._sequence.add_edge(0, 2, None) + ir_example._sequence.add_edge(0, 3, None) + self.assertEqual(ir_example.initial_time(), None) + def test_final_time(self): """Test final time""" ir_example = SequenceIR(AlignLeft()) @@ -88,6 +100,18 @@ def test_final_time(self): ir_example._sequence.add_edge(4, 1, None) self.assertEqual(ir_example.final_time(), 300) + def test_final_time_partial_scheduling(self): + """Test final time with partial scheduling""" + ir_example = SequenceIR(AlignLeft()) + inst = Play(Constant(100, 0.5), frame=QubitFrame(1), target=Qubit(1)) + ir_example.append(inst) + ir_example.append(inst) + ir_example._time_table[2] = 1000 + ir_example._time_table[3] = None + ir_example._sequence.add_edge(2, 1, None) + ir_example._sequence.add_edge(3, 1, None) + self.assertEqual(ir_example.final_time(), None) + def test_duration(self): """Test duration""" ir_example = SequenceIR(AlignLeft()) From 13393b4b800f5a46dcafb916156302879cd1a4da Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Fri, 8 Mar 2024 10:20:37 +0200 Subject: [PATCH 10/15] Disable lint --- qiskit/pulse/ir/ir.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/pulse/ir/ir.py b/qiskit/pulse/ir/ir.py index d35f56da0826..e07edf133911 100644 --- a/qiskit/pulse/ir/ir.py +++ b/qiskit/pulse/ir/ir.py @@ -213,6 +213,7 @@ def _draw_nodes(n): node_attr_fn=_draw_nodes, ) + # pylint: disable=cell-var-from-loop def flatten(self, inplace: bool = False) -> SequenceIR: """Recursively flatten the SequenceIR. From c828ab5442e2f5ba55c0ecd05801dc2ac554e000 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Sun, 10 Mar 2024 00:43:24 +0200 Subject: [PATCH 11/15] MapMixedFrame + SetSequence passes --- qiskit/pulse/compiler/__init__.py | 1 + qiskit/pulse/compiler/passes/__init__.py | 3 + .../pulse/compiler/passes/map_mixed_frames.py | 59 +++ qiskit/pulse/compiler/passes/set_sequence.py | 53 +++ qiskit/pulse/transforms/__init__.py | 2 + qiskit/pulse/transforms/alignments.py | 151 +++++-- test/python/pulse/compiler_passes/__init__.py | 13 + .../compiler_passes/test_map_mixed_frames.py | 106 +++++ .../compiler_passes/test_set_sequence.py | 379 ++++++++++++++++++ test/python/pulse/compiler_passes/utils.py | 61 +++ 10 files changed, 803 insertions(+), 25 deletions(-) create mode 100644 qiskit/pulse/compiler/passes/map_mixed_frames.py create mode 100644 qiskit/pulse/compiler/passes/set_sequence.py create mode 100644 test/python/pulse/compiler_passes/__init__.py create mode 100644 test/python/pulse/compiler_passes/test_map_mixed_frames.py create mode 100644 test/python/pulse/compiler_passes/test_set_sequence.py create mode 100644 test/python/pulse/compiler_passes/utils.py diff --git a/qiskit/pulse/compiler/__init__.py b/qiskit/pulse/compiler/__init__.py index e8bd600b1a05..0165f484bc35 100644 --- a/qiskit/pulse/compiler/__init__.py +++ b/qiskit/pulse/compiler/__init__.py @@ -13,3 +13,4 @@ """Pass-based Qiskit pulse program compiler.""" from .passmanager import BlockTranspiler, BlockToIrCompiler +from .passes import MapMixedFrame, SetSequence diff --git a/qiskit/pulse/compiler/passes/__init__.py b/qiskit/pulse/compiler/passes/__init__.py index d0807e55c63d..9c6186803696 100644 --- a/qiskit/pulse/compiler/passes/__init__.py +++ b/qiskit/pulse/compiler/passes/__init__.py @@ -11,3 +11,6 @@ # that they have been altered from the originals. """Built-in pulse compile passes.""" + +from .map_mixed_frames import MapMixedFrame +from .set_sequence import SetSequence diff --git a/qiskit/pulse/compiler/passes/map_mixed_frames.py b/qiskit/pulse/compiler/passes/map_mixed_frames.py new file mode 100644 index 000000000000..4eb9c2d2275a --- /dev/null +++ b/qiskit/pulse/compiler/passes/map_mixed_frames.py @@ -0,0 +1,59 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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. + +"""A base pass for Qiskit PulseIR compilation.""" + +from __future__ import annotations +from collections import defaultdict + +from qiskit.pulse.compiler.basepasses import AnalysisPass +from qiskit.pulse.ir import SequenceIR +from qiskit.pulse.model import MixedFrame + + +class MapMixedFrame(AnalysisPass): + """Map the dependencies of all ``MixedFrame``s on ``PulseTaraget`` and ``Frame``. + + The pass recursively scans the ``SequenceIR``, identifies all ``MixedFrame``s and + tracks the dependencies of them on ``PulseTarget`` and ``Frame``. The analysis result + is added as a dictionary to the property set under key "mixed_frames_mapping". The + added dictionary is keyed on every ``PulseTarget`` and ``Frame`` in ``SequenceIR`` + with the value being a set of all ``MixedFrame``s associated with the key. + """ + + def __init__(self): + """Create new MapMixedFrames pass""" + super().__init__(target=None) + self.mixed_frames_mapping = defaultdict(set) + + def run( + self, + passmanager_ir: SequenceIR, + ) -> None: + + self._analyze_mixed_frames_in_sequence(passmanager_ir) + self.property_set["mixed_frames_mapping"] = self.mixed_frames_mapping + + def _analyze_mixed_frames_in_sequence(self, prog: SequenceIR) -> None: + """A helper function to recurse through the sequence while mapping mixed frame dependency""" + for elm in prog.elements(): + # Sub Block + if isinstance(elm, SequenceIR): + self._analyze_mixed_frames_in_sequence(elm) + # Pulse Instruction + else: + if isinstance(inst_target := elm.inst_target, MixedFrame): + self.mixed_frames_mapping[inst_target.frame].add(inst_target) + self.mixed_frames_mapping[inst_target.pulse_target].add(inst_target) + + def __hash__(self): + return hash((self.__class__.__name__,)) diff --git a/qiskit/pulse/compiler/passes/set_sequence.py b/qiskit/pulse/compiler/passes/set_sequence.py new file mode 100644 index 000000000000..c6a6316ddd87 --- /dev/null +++ b/qiskit/pulse/compiler/passes/set_sequence.py @@ -0,0 +1,53 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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. + +"""A base pass for Qiskit PulseIR compilation.""" + +from __future__ import annotations + +from qiskit.pulse.compiler.basepasses import TransformationPass +from qiskit.pulse.ir import SequenceIR + + +class SetSequence(TransformationPass): + """Map the dependencies of all ``MixedFrame``s on ``PulseTaraget`` and ``Frame``. + + The pass recursively scans the ``SequenceIR``, identifies all ``MixedFrame``s and + tracks the dependencies of them on ``PulseTarget`` and ``Frame``. The analysis result + is added as a dictionary to the property set under key "mixed_frames_mapping". The + added dictionary is keyed on every ``PulseTarget`` and ``Frame`` in ``SequenceIR`` + with the value being a set of all ``MixedFrame``s associated with the key. + """ + + def __init__(self): + """Create new MapMixedFrames pass""" + super().__init__(target=None) + + def run( + self, + passmanager_ir: SequenceIR, + ) -> SequenceIR: + + self._set_sequence_recursion(passmanager_ir) + return passmanager_ir + + def _set_sequence_recursion(self, prog: SequenceIR) -> None: + """A helper function to recurse through the sequence""" + prog.alignment.set_sequence( + prog.sequence, mixed_frames_mapping=self.property_set["mixed_frames_mapping"] + ) + for elm in prog.elements(): + if isinstance(elm, SequenceIR): + self._set_sequence_recursion(elm) + + def __hash__(self): + return hash((self.__class__.__name__,)) diff --git a/qiskit/pulse/transforms/__init__.py b/qiskit/pulse/transforms/__init__.py index 4fb4bf40e9ef..b84b6ceffd05 100644 --- a/qiskit/pulse/transforms/__init__.py +++ b/qiskit/pulse/transforms/__init__.py @@ -87,6 +87,8 @@ AlignRight, AlignSequential, AlignmentKind, + ParallelAlignment, + SequentialAlignment, ) from .base_transforms import target_qobj_transform diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py index 569219777f20..27c00b941b0e 100644 --- a/qiskit/pulse/transforms/alignments.py +++ b/qiskit/pulse/transforms/alignments.py @@ -13,6 +13,7 @@ from __future__ import annotations import abc from typing import Callable, Tuple +from rustworkx import PyDAG import numpy as np @@ -20,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.model import MixedFrame class AlignmentKind(abc.ABC): @@ -44,6 +46,22 @@ def align(self, schedule: Schedule) -> Schedule: """ pass + @abc.abstractmethod + def set_sequence(self, sequence: PyDAG, **kwargs) -> None: + """Set the sequence of the IR program according to the policy. + + The ``sequence`` is mutated to include all the edges + connecting the elements in the sequence. + + Only top-level elements are sequences. If sub-sequences are nested, + nested sequences are not recursively set. + + Args: + sequence: The graph object to be sequenced. + kwargs: Keyword arguments needed for some subclasses. + """ + pass + @property @abc.abstractmethod def is_sequential(self) -> bool: @@ -87,7 +105,110 @@ def __repr__(self): return f"{self.__class__.__name__}({', '.join(self._context_params)})" -class AlignLeft(AlignmentKind): +class SequentialAlignment(AlignmentKind, abc.ABC): + """Abstract base class for ``AlignmentKind`` which aligns instructions sequentially""" + + def set_sequence(self, sequence: PyDAG, **kwargs) -> None: + """Sets the sequence sequentially. + + The ``sequence`` property of ``ir_program`` is mutated to include all the edges + connecting the elements of the sequence sequentially according to their order. + + Only top-level elements are sequences. If sub-sequences are nested, + nested sequences are not recursively set. + + Args: + sequence: The graph object to be sequenced. + kwargs: Included only to match the signature of the function with other subclasses. + """ + nodes = sequence.node_indices() + prev = 0 + # The first two nodes are the in\out nodes. + for ind in nodes[2:]: + sequence.add_edge(prev, ind, None) + prev = ind + sequence.add_edge(prev, 1, None) + + @property + def is_sequential(self) -> bool: + return True + + +class ParallelAlignment(AlignmentKind, abc.ABC): + """Abstract base class for ``AlignmentKind`` which aligns instructions in parallel""" + + def set_sequence(self, sequence: PyDAG, **kwargs) -> None: + """Sets the sequence in parallel. + + The ``sequence`` property of ``ir_program`` is mutated to include all the edges + connecting the elements of the sequence in parallel. + + The function requires a ``mixed_frame_mapping`` dictionary - mapping all ``PulseTarget`` + and ``Frame`` to the associated ``MixedFrame`` - to be passed to ``kwargs``. As part + of a pass manager work flow the dictionary is obtained via the pass + :class:`~qiskit.pulse.compiler.MapMixedFrames`. + + Only top-level elements are sequenced. If sub-sequences are nested, + nested sequences are not recursively set. + + Args: + sequence: The graph object to be sequenced. + kwargs: Expecting a keyword argument ``mixed_frame_mapping``. + + Raises: + PulseError: if ``kwargs`` does not include a "mixed_frames_mapping" key. + """ + if "mixed_frames_mapping" not in kwargs.keys() or kwargs["mixed_frames_mapping"] is None: + raise PulseError( + "Expected a keyword argument mixed_frames_mapping with a" + " mapping of PulseTarget and Frame to MixedFrame" + ) + mixed_frame_mapping = kwargs["mixed_frames_mapping"] + + idle_after = {} + for ind in sequence.node_indices(): + if ind in (0, 1): + # In, Out node + continue + node = sequence.get_node_data(ind) + node_mixed_frames = set() + + # if isinstance(node, SequenceIR): + # inst_targets = node.inst_targets + # else: + # inst_targets = [node.inst_target] + if hasattr(node, "inst_targets"): + # SequenceIR object + inst_targets = node.inst_targets + else: + # Instruction object + inst_targets = [node.inst_target] + + for inst_target in inst_targets: + if isinstance(inst_target, MixedFrame): + node_mixed_frames.add(inst_target) + else: + node_mixed_frames |= mixed_frame_mapping[inst_target] + + pred_nodes = [ + idle_after[mixed_frame] + for mixed_frame in node_mixed_frames + if mixed_frame in idle_after + ] + if len(pred_nodes) == 0: + pred_nodes = [0] + for pred_node in pred_nodes: + sequence.add_edge(pred_node, ind, None) + for mixed_frame in node_mixed_frames: + idle_after[mixed_frame] = ind + sequence.add_edges_from_no_data([(ni, 1) for ni in idle_after.values()]) + + @property + def is_sequential(self) -> bool: + return False + + +class AlignLeft(ParallelAlignment): """Align instructions in as-soon-as-possible manner. Instructions are placed at earliest available timeslots. @@ -97,10 +218,6 @@ def __init__(self): """Create new left-justified context.""" super().__init__(context_params=()) - @property - def is_sequential(self) -> bool: - return False - def align(self, schedule: Schedule) -> Schedule: """Reallocate instructions according to the policy. @@ -154,7 +271,7 @@ def _push_left_append(this: Schedule, other: ScheduleComponent) -> Schedule: return this.insert(insert_time, other, inplace=True) -class AlignRight(AlignmentKind): +class AlignRight(ParallelAlignment): """Align instructions in as-late-as-possible manner. Instructions are placed at latest available timeslots. @@ -164,10 +281,6 @@ def __init__(self): """Create new right-justified context.""" super().__init__(context_params=()) - @property - def is_sequential(self) -> bool: - return False - def align(self, schedule: Schedule) -> Schedule: """Reallocate instructions according to the policy. @@ -222,7 +335,7 @@ def _push_right_prepend(this: Schedule, other: ScheduleComponent) -> Schedule: return this -class AlignSequential(AlignmentKind): +class AlignSequential(SequentialAlignment): """Align instructions sequentially. Instructions played on different channels are also arranged in a sequence. @@ -233,10 +346,6 @@ def __init__(self): """Create new sequential context.""" super().__init__(context_params=()) - @property - def is_sequential(self) -> bool: - return True - def align(self, schedule: Schedule) -> Schedule: """Reallocate instructions according to the policy. @@ -256,7 +365,7 @@ def align(self, schedule: Schedule) -> Schedule: return aligned -class AlignEquispaced(AlignmentKind): +class AlignEquispaced(SequentialAlignment): """Align instructions with equispaced interval within a specified duration. Instructions played on different channels are also arranged in a sequence. @@ -274,10 +383,6 @@ def __init__(self, duration: int | ParameterExpression): """ super().__init__(context_params=(duration,)) - @property - def is_sequential(self) -> bool: - return True - @property def duration(self): """Return context duration.""" @@ -325,7 +430,7 @@ def align(self, schedule: Schedule) -> Schedule: return aligned -class AlignFunc(AlignmentKind): +class AlignFunc(SequentialAlignment): """Allocate instructions at position specified by callback function. The position is specified for each instruction of index ``j`` as a @@ -362,10 +467,6 @@ def __init__(self, duration: int | ParameterExpression, func: Callable): """ super().__init__(context_params=(duration, func)) - @property - def is_sequential(self) -> bool: - return True - @property def duration(self): """Return context duration.""" diff --git a/test/python/pulse/compiler_passes/__init__.py b/test/python/pulse/compiler_passes/__init__.py new file mode 100644 index 000000000000..e811c8a3d61e --- /dev/null +++ b/test/python/pulse/compiler_passes/__init__.py @@ -0,0 +1,13 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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. + +"""Qiskit pulse compiler tests.""" diff --git a/test/python/pulse/compiler_passes/test_map_mixed_frames.py b/test/python/pulse/compiler_passes/test_map_mixed_frames.py new file mode 100644 index 000000000000..dcabaeeb1999 --- /dev/null +++ b/test/python/pulse/compiler_passes/test_map_mixed_frames.py @@ -0,0 +1,106 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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 MapMixedFrames""" +from test import QiskitTestCase + +from qiskit.pulse import ( + Constant, + Play, + Delay, + ShiftPhase, +) + +from qiskit.pulse.ir import ( + SequenceIR, +) + +from qiskit.pulse.model import QubitFrame, Qubit, MixedFrame +from qiskit.pulse.transforms import AlignLeft +from qiskit.pulse.compiler import MapMixedFrame + + +class TestMapMixedFrames(QiskitTestCase): + """Test MapMixedFrames analysis pass""" + + def test_basic_ir(self): + """test with basic IR""" + ir_example = SequenceIR(AlignLeft()) + mf = MixedFrame(Qubit(0), QubitFrame(1)) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf)) + + mapping_pass = MapMixedFrame() + mapping_pass.run(ir_example) + self.assertEqual(len(mapping_pass.property_set.keys()), 1) + mapping = mapping_pass.property_set["mixed_frames_mapping"] + self.assertEqual(len(mapping), 2) + self.assertEqual(mapping[mf.pulse_target], {mf}) + self.assertEqual(mapping[mf.frame], {mf}) + + mf2 = MixedFrame(Qubit(0), QubitFrame(2)) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf2)) + + mapping_pass.run(ir_example) + self.assertEqual(len(mapping), 3) + self.assertEqual(mapping[mf.pulse_target], {mf, mf2}) + self.assertEqual(mapping[mf.frame], {mf}) + + def test_with_several_inst_target_types(self): + """test with different inst_target types""" + ir_example = SequenceIR(AlignLeft()) + mf = MixedFrame(Qubit(0), QubitFrame(1)) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf)) + ir_example.append(Delay(100, target=Qubit(2))) + ir_example.append(ShiftPhase(100, frame=QubitFrame(2))) + + mapping_pass = MapMixedFrame() + mapping_pass.run(ir_example) + mapping = mapping_pass.property_set["mixed_frames_mapping"] + self.assertEqual(len(mapping), 2) + self.assertEqual(mapping[Qubit(0)], {mf}) + self.assertEqual(mapping[QubitFrame(1)], {mf}) + + def test_with_sub_blocks(self): + """test with sub blocks""" + mf1 = MixedFrame(Qubit(0), QubitFrame(0)) + mf2 = MixedFrame(Qubit(0), QubitFrame(1)) + mf3 = MixedFrame(Qubit(0), QubitFrame(2)) + + sub_block_2 = SequenceIR(AlignLeft()) + sub_block_2.append(Play(Constant(100, 0.1), mixed_frame=mf1)) + + sub_block_1 = SequenceIR(AlignLeft()) + sub_block_1.append(Play(Constant(100, 0.1), mixed_frame=mf2)) + sub_block_1.append(sub_block_2) + + mapping_pass = MapMixedFrame() + mapping_pass.run(sub_block_1) + mapping = mapping_pass.property_set["mixed_frames_mapping"] + + self.assertEqual(len(mapping), 3) + self.assertEqual(mapping[Qubit(0)], {mf1, mf2}) + self.assertEqual(mapping[QubitFrame(0)], {mf1}) + self.assertEqual(mapping[QubitFrame(1)], {mf2}) + + ir_example = SequenceIR(AlignLeft()) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf3)) + ir_example.append(sub_block_1) + + mapping_pass = MapMixedFrame() + mapping_pass.run(ir_example) + mapping = mapping_pass.property_set["mixed_frames_mapping"] + + self.assertEqual(len(mapping), 4) + self.assertEqual(mapping[Qubit(0)], {mf1, mf2, mf3}) + self.assertEqual(mapping[QubitFrame(0)], {mf1}) + self.assertEqual(mapping[QubitFrame(1)], {mf2}) + self.assertEqual(mapping[QubitFrame(2)], {mf3}) diff --git a/test/python/pulse/compiler_passes/test_set_sequence.py b/test/python/pulse/compiler_passes/test_set_sequence.py new file mode 100644 index 000000000000..36d27e14cf62 --- /dev/null +++ b/test/python/pulse/compiler_passes/test_set_sequence.py @@ -0,0 +1,379 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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 SetSequence""" +from test import QiskitTestCase +from ddt import ddt, named_data, unpack + +from qiskit.pulse import ( + Constant, + Play, + Delay, + ShiftPhase, +) + +from qiskit.pulse.ir import ( + SequenceIR, +) + +from qiskit.pulse.model import QubitFrame, Qubit, MixedFrame +from qiskit.pulse.transforms import ( + AlignLeft, + AlignRight, + AlignSequential, + AlignFunc, + AlignEquispaced, +) +from qiskit.pulse.compiler import MapMixedFrame, SetSequence +from qiskit.pulse.exceptions import PulseError +from .utils import PulseIrTranspiler + + +@ddt +class TestSetSequenceParallelAlignment(QiskitTestCase): + """Test SetSequence pass with Parallel Alignment""" + + ddt_named_data = [["align_left", AlignLeft()], ["align_right", AlignRight()]] + + def _get_pm(self) -> PulseIrTranspiler: + pm = PulseIrTranspiler() + pm.append(MapMixedFrame()) + pm.append(SetSequence()) + return pm + + @named_data(*ddt_named_data) + @unpack + def test_no_mapping_pass_error(self, alignment): + """test that running without MapMixedFrame pass raises a PulseError""" + + pm = PulseIrTranspiler() + pm.append(SetSequence()) + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + with self.assertRaises(PulseError): + pm.run(ir_example) + + @named_data(*ddt_named_data) + @unpack + def test_single_instruction(self, alignment): + """test with a single instruction""" + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = self._get_pm().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 2) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 1) in edge_list) + + # TODO: Take care of this weird edge case + # def test_instruction_not_in_mapping(self): + # """test with an instruction which is not in the mapping""" + # + # ir_example = SequenceIR(AlignLeft()) + # ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + # ir_example.append(Delay(100, target=Qubit(5))) + # + # property_set = {} + # analyze_target_frame_pass(ir_example, property_set) + # ir_example = sequence_pass(ir_example, property_set) + # edge_list = ir_example.sequence.edge_list() + # self.assertEqual(len(edge_list), 4) + # self.assertTrue((0, 2) in edge_list) + # self.assertTrue((0, 3) in edge_list) + # self.assertTrue((2, 1) in edge_list) + # self.assertTrue((3, 1) in edge_list) + # + + @named_data(*ddt_named_data) + @unpack + def test_parallel_instructions(self, alignment): + """test with two parallel instructions""" + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + + ir_example = self._get_pm().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 4) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 1) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((3, 1) in edge_list) + + @named_data(*ddt_named_data) + @unpack + def test_sequential_instructions(self, alignment): + """test with two sequential instructions""" + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = self._get_pm().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 3) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 3) in edge_list) + self.assertTrue((3, 1) in edge_list) + + @named_data(*ddt_named_data) + @unpack + def test_pulse_target_instruction_broadcasting_to_children(self, alignment): + """test with an instruction which is defined on a PulseTarget and is + broadcasted to several children""" + + ir_example = SequenceIR(alignment) + ir_example.append(Delay(100, target=Qubit(0))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) + + ir_example = self._get_pm().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 5) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 3) in edge_list) + self.assertTrue((2, 4) in edge_list) + self.assertTrue((3, 1) in edge_list) + self.assertTrue((4, 1) in edge_list) + + @named_data(*ddt_named_data) + @unpack + def test_frame_instruction_broadcasting_to_children(self, alignment): + """test with an instruction which is defined on a Frame and is broadcasted to several children""" + + ir_example = SequenceIR(alignment) + ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) + + ir_example = self._get_pm().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 5) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 3) in edge_list) + self.assertTrue((2, 4) in edge_list) + self.assertTrue((3, 1) in edge_list) + self.assertTrue((4, 1) in edge_list) + + @named_data(*ddt_named_data) + @unpack + def test_pulse_target_instruction_dependency(self, alignment): + """test with an instruction which is defined on a PulseTarget and depends on + several mixed frames""" + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) + ir_example.append(Delay(100, target=Qubit(0))) + + ir_example = self._get_pm().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 5) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((2, 4) in edge_list) + self.assertTrue((3, 4) in edge_list) + self.assertTrue((4, 1) in edge_list) + + @named_data(*ddt_named_data) + @unpack + def test_frame_instruction_dependency(self, alignment): + """test with an instruction which is defined on a Frame and depends on several mixed frames""" + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) + ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) + + ir_example = self._get_pm().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 5) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((2, 4) in edge_list) + self.assertTrue((3, 4) in edge_list) + self.assertTrue((4, 1) in edge_list) + + @named_data(*ddt_named_data) + @unpack + def test_recursion_to_sub_blocks(self, alignment): + """test that sequencing is recursively applied to sub blocks""" + + sub_block = SequenceIR(alignment) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + + ir_example = self._get_pm().run(ir_example) + edge_list_sub_block = ir_example.elements()[1].sequence.edge_list() + self.assertEqual(len(edge_list_sub_block), 2) + self.assertTrue((0, 2) in edge_list_sub_block) + self.assertTrue((2, 1) in edge_list_sub_block) + + @named_data(*ddt_named_data) + @unpack + def test_with_parallel_sub_block(self, alignment): + """test with a sub block which doesn't depend on previous instructions""" + + sub_block = SequenceIR(alignment) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + + ir_example = self._get_pm().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 4) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((2, 1) in edge_list) + self.assertTrue((3, 1) in edge_list) + + @named_data(*ddt_named_data) + @unpack + def test_with_simple_sequential_sub_block(self, alignment): + """test with a sub block which depends on a single previous instruction""" + + sub_block = SequenceIR(alignment) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(sub_block) + + ir_example = self._get_pm().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 5) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((3, 4) in edge_list) + self.assertTrue((2, 1) in edge_list) + self.assertTrue((4, 1) in edge_list) + + @named_data(*ddt_named_data) + @unpack + def test_with_sequential_sub_block_with_more_dependencies(self, alignment): + """test with a sub block which depends on several previous instruction""" + + sub_block = SequenceIR(alignment) + sub_block.append(Delay(100, target=Qubit(0))) + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + ir_example.append(sub_block) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + + ir_example = self._get_pm().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 8) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((0, 3) in edge_list) + self.assertTrue((0, 4) in edge_list) + self.assertTrue((2, 5) in edge_list) + self.assertTrue((3, 5) in edge_list) + self.assertTrue((4, 6) in edge_list) + self.assertTrue((5, 1) in edge_list) + self.assertTrue((6, 1) in edge_list) + + +@ddt +class TestSetSequenceSequentialAlignment(QiskitTestCase): + """Test SetSequence pass with Sequential Alignment""" + + ddt_named_data = [ + ["align_sequential", AlignSequential()], + ["align_func", AlignFunc(100, lambda x: x)], + ["align_equispaced", AlignEquispaced(100)], + ] + + @named_data(*ddt_named_data) + @unpack + def test_single_instruction(self, alignment): + """test with a single instruction""" + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = SetSequence().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 2) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 1) in edge_list) + + @named_data(*ddt_named_data) + @unpack + def test_several_instructions(self, alignment): + """test with several instructions""" + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(2), QubitFrame(1)))) + + ir_example = SetSequence().run(ir_example) + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 4) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 3) in edge_list) + self.assertTrue((3, 4) in edge_list) + self.assertTrue((4, 1) in edge_list) + + @named_data(*ddt_named_data) + @unpack + def test_recursion_to_sub_blocks(self, alignment): + """test that sequencing is recursively applied to sub blocks""" + + sub_block = SequenceIR(alignment) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + + ir_example = SetSequence().run(ir_example) + + edge_list_sub_block = ir_example.elements()[1].sequence.edge_list() + self.assertEqual(len(edge_list_sub_block), 2) + self.assertTrue((0, 2) in edge_list_sub_block) + self.assertTrue((2, 1) in edge_list_sub_block) + + @named_data(*ddt_named_data) + @unpack + def test_sub_blocks_and_instructions(self, alignment): + """test sequencing with a mix of instructions and sub blocks""" + + sub_block = SequenceIR(alignment) + sub_block.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) + ir_example.append(sub_block) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) + + ir_example = SetSequence().run(ir_example) + + edge_list = ir_example.sequence.edge_list() + self.assertEqual(len(edge_list), 4) + self.assertTrue((0, 2) in edge_list) + self.assertTrue((2, 3) in edge_list) + self.assertTrue((3, 4) in edge_list) + self.assertTrue((4, 1) in edge_list) diff --git a/test/python/pulse/compiler_passes/utils.py b/test/python/pulse/compiler_passes/utils.py new file mode 100644 index 000000000000..23d1f3608ead --- /dev/null +++ b/test/python/pulse/compiler_passes/utils.py @@ -0,0 +1,61 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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. + +"""Utilities for Pulse Compiler passes tests""" + +from __future__ import annotations + +from abc import ABC +from collections.abc import Callable +from typing import Any + +from qiskit.passmanager import BasePassManager +from qiskit.pulse.ir import SequenceIR + + +PulseProgramT = Any +"""Type alias representing whatever pulse programs.""" + + +class PulseIrTranspiler(BasePassManager, ABC): + """Pass manager for Pulse IR -> Pulse IR transpilation""" + + def _passmanager_frontend( + self, + input_program: SequenceIR, + **kwargs, + ) -> SequenceIR: + + return input_program + + def _passmanager_backend( + self, + passmanager_ir: SequenceIR, + in_program: SequenceIR, + **kwargs, + ) -> SequenceIR: + + return passmanager_ir + + # pylint: disable=arguments-differ + def run( + self, + pulse_programs: SequenceIR | list[SequenceIR], + callback: Callable | None = None, + num_processes: int | None = None, + ) -> PulseProgramT | list[PulseProgramT]: + """Run all the passes on the input pulse programs.""" + return super().run( + in_programs=pulse_programs, + callback=callback, + num_processes=num_processes, + ) From 50ec91671a1163707797e5a476326592e2bf041b Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Sun, 10 Mar 2024 10:54:57 +0200 Subject: [PATCH 12/15] Doc fixes --- qiskit/pulse/compiler/passes/map_mixed_frames.py | 2 +- qiskit/pulse/compiler/passes/set_sequence.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/qiskit/pulse/compiler/passes/map_mixed_frames.py b/qiskit/pulse/compiler/passes/map_mixed_frames.py index 4eb9c2d2275a..f6acf030568b 100644 --- a/qiskit/pulse/compiler/passes/map_mixed_frames.py +++ b/qiskit/pulse/compiler/passes/map_mixed_frames.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""A base pass for Qiskit PulseIR compilation.""" +"""MixedFrames mapping analysis pass""" from __future__ import annotations from collections import defaultdict diff --git a/qiskit/pulse/compiler/passes/set_sequence.py b/qiskit/pulse/compiler/passes/set_sequence.py index c6a6316ddd87..ddc33393ad9f 100644 --- a/qiskit/pulse/compiler/passes/set_sequence.py +++ b/qiskit/pulse/compiler/passes/set_sequence.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""A base pass for Qiskit PulseIR compilation.""" +"""Sequencing pass for Qiskit PulseIR compilation.""" from __future__ import annotations @@ -19,17 +19,17 @@ class SetSequence(TransformationPass): - """Map the dependencies of all ``MixedFrame``s on ``PulseTaraget`` and ``Frame``. + """Sets the sequence of a ``SequenceIR`` object. - The pass recursively scans the ``SequenceIR``, identifies all ``MixedFrame``s and - tracks the dependencies of them on ``PulseTarget`` and ``Frame``. The analysis result - is added as a dictionary to the property set under key "mixed_frames_mapping". The - added dictionary is keyed on every ``PulseTarget`` and ``Frame`` in ``SequenceIR`` - with the value being a set of all ``MixedFrame``s associated with the key. + The pass traverses the ``SequenceIR``, recursively sets the sequence, by adding edges to + the ``sequence`` property. Sequencing is done according to the alignment strategy. + + For parallel alignment types, the pass depends on the results of the analysis pass + :class:`~qiskit.pulse.compiler.passes.MapMixedFrame`. """ def __init__(self): - """Create new MapMixedFrames pass""" + """Create new SetSequence pass""" super().__init__(target=None) def run( From e185741e017764ab66aa04187f959ea6179afaff Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:18:05 +0200 Subject: [PATCH 13/15] Corrections --- .../pulse/compiler/passes/map_mixed_frames.py | 40 +++--- qiskit/pulse/compiler/passes/set_sequence.py | 119 ++++++++++++++++-- qiskit/pulse/exceptions.py | 4 + qiskit/pulse/transforms/alignments.py | 105 ---------------- .../compiler_passes/test_map_mixed_frames.py | 6 + .../compiler_passes/test_set_sequence.py | 79 +++++------- 6 files changed, 168 insertions(+), 185 deletions(-) diff --git a/qiskit/pulse/compiler/passes/map_mixed_frames.py b/qiskit/pulse/compiler/passes/map_mixed_frames.py index f6acf030568b..0170de598d78 100644 --- a/qiskit/pulse/compiler/passes/map_mixed_frames.py +++ b/qiskit/pulse/compiler/passes/map_mixed_frames.py @@ -21,39 +21,37 @@ class MapMixedFrame(AnalysisPass): - """Map the dependencies of all ``MixedFrame``s on ``PulseTaraget`` and ``Frame``. + r"""Map the dependencies of all class:`.MixedFrame`\s + on class:`~qiskit.pulse.PulseTaraget` and :class:`~qiskit.pulse.Frame`. - The pass recursively scans the ``SequenceIR``, identifies all ``MixedFrame``s and - tracks the dependencies of them on ``PulseTarget`` and ``Frame``. The analysis result + The pass recursively scans the :class:`.SequenceIR`, identifies all :class:`.MixedFrame`\s and + tracks the dependencies of them on class:`~qiskit.pulse.PulseTaraget` and + :class:`~qiskit.pulse.Frame`. The analysis result is added as a dictionary to the property set under key "mixed_frames_mapping". The - added dictionary is keyed on every ``PulseTarget`` and ``Frame`` in ``SequenceIR`` - with the value being a set of all ``MixedFrame``s associated with the key. + added dictionary is keyed on every class:`~qiskit.pulse.PulseTaraget` and + :class:`~qiskit.pulse.Frame` in :class:`.SequenceIR` + with the value being a set of all class:`.MixedFrame`\s associated with the key. """ def __init__(self): - """Create new MapMixedFrames pass""" + """Create new ``MapMixedFrames`` pass""" super().__init__(target=None) - self.mixed_frames_mapping = defaultdict(set) def run( self, passmanager_ir: SequenceIR, ) -> None: + """Run ``MapMixedFrame`` pass""" + mixed_frames_mapping = defaultdict(set) - self._analyze_mixed_frames_in_sequence(passmanager_ir) - self.property_set["mixed_frames_mapping"] = self.mixed_frames_mapping - - def _analyze_mixed_frames_in_sequence(self, prog: SequenceIR) -> None: - """A helper function to recurse through the sequence while mapping mixed frame dependency""" - for elm in prog.elements(): - # Sub Block - if isinstance(elm, SequenceIR): - self._analyze_mixed_frames_in_sequence(elm) - # Pulse Instruction - else: - if isinstance(inst_target := elm.inst_target, MixedFrame): - self.mixed_frames_mapping[inst_target.frame].add(inst_target) - self.mixed_frames_mapping[inst_target.pulse_target].add(inst_target) + for inst_target in passmanager_ir.inst_targets: + if isinstance(inst_target, MixedFrame): + mixed_frames_mapping[inst_target.frame].add(inst_target) + mixed_frames_mapping[inst_target.pulse_target].add(inst_target) + self.property_set["mixed_frames_mapping"] = mixed_frames_mapping def __hash__(self): return hash((self.__class__.__name__,)) + + def __eq__(self, other): + return self.__class__.__name__ == other.__class__.__name__ diff --git a/qiskit/pulse/compiler/passes/set_sequence.py b/qiskit/pulse/compiler/passes/set_sequence.py index ddc33393ad9f..b6118d978d64 100644 --- a/qiskit/pulse/compiler/passes/set_sequence.py +++ b/qiskit/pulse/compiler/passes/set_sequence.py @@ -13,15 +13,20 @@ """Sequencing pass for Qiskit PulseIR compilation.""" from __future__ import annotations +from functools import singledispatchmethod +from rustworkx import PyDAG from qiskit.pulse.compiler.basepasses import TransformationPass from qiskit.pulse.ir import SequenceIR +from qiskit.pulse.model import MixedFrame +from qiskit.pulse.transforms import AlignmentKind, SequentialAlignment, ParallelAlignment +from qiskit.pulse.exceptions import PulseCompilerError class SetSequence(TransformationPass): - """Sets the sequence of a ``SequenceIR`` object. + """Sets the sequence of a :class:`.SequenceIR` object. - The pass traverses the ``SequenceIR``, recursively sets the sequence, by adding edges to + The pass traverses the :class:`.SequenceIR` and recursively sets the sequence, by adding edges to the ``sequence`` property. Sequencing is done according to the alignment strategy. For parallel alignment types, the pass depends on the results of the analysis pass @@ -36,18 +41,110 @@ def run( self, passmanager_ir: SequenceIR, ) -> SequenceIR: + """Run sequencing pass. - self._set_sequence_recursion(passmanager_ir) + Arguments: + passmanager_ir: The IR object to be sequenced. + """ + + self._sequence_instructions(passmanager_ir.alignment, passmanager_ir.sequence) return passmanager_ir - def _set_sequence_recursion(self, prog: SequenceIR) -> None: - """A helper function to recurse through the sequence""" - prog.alignment.set_sequence( - prog.sequence, mixed_frames_mapping=self.property_set["mixed_frames_mapping"] - ) - for elm in prog.elements(): - if isinstance(elm, SequenceIR): - self._set_sequence_recursion(elm) + @singledispatchmethod + def _sequence_instructions(self, alignment: AlignmentKind, sequence: PyDAG): + """Finalize the sequence by adding edges to the DAG + + ``sequence`` is mutated to include all the edges + connecting the elements of the sequence. + + Nested :class:`.SequenceIR` objects are sequenced recursively. + """ + raise NotImplementedError + + # pylint: disable=unused-argument + @_sequence_instructions.register(ParallelAlignment) + def _sequence_instructions_parallel(self, alignment: ParallelAlignment, sequence: PyDAG): + """Finalize the sequence by adding edges to the DAG, following parallel alignment. + + ``sequence`` is mutated to include all the edges + connecting the elements of the sequence in parallel. + + Nested :class:`.SequenceIR` objects are sequenced recursively. + + Args: + alignment: The IR alignment. + sequence: The graph object to be sequenced. + + Raises: + PulseCompilerError: if ``property_set`` does not include a mixed_frames_mapping dictionary. + """ + if self.property_set["mixed_frames_mapping"] is None: + raise PulseCompilerError( + "Parallel sequencing requires mixed frames mapping." + " Run MapMixedFrame before sequencing" + ) + + mixed_frame_mapping = self.property_set["mixed_frames_mapping"] + + idle_after = {} + for ind in sequence.node_indices(): + if ind in (0, 1): + # In, Out node + continue + node = sequence.get_node_data(ind) + node_mixed_frames = set() + + if isinstance(node, SequenceIR): + self._sequence_instructions(node.alignment, node.sequence) + inst_targets = node.inst_targets + else: + inst_targets = [node.inst_target] + + for inst_target in inst_targets: + if isinstance(inst_target, MixedFrame): + node_mixed_frames.add(inst_target) + else: + node_mixed_frames |= mixed_frame_mapping[inst_target] + + pred_nodes = [ + idle_after[mixed_frame] + for mixed_frame in node_mixed_frames + if mixed_frame in idle_after + ] + if len(pred_nodes) == 0: + pred_nodes = [0] + for pred_node in pred_nodes: + sequence.add_edge(pred_node, ind, None) + for mixed_frame in node_mixed_frames: + idle_after[mixed_frame] = ind + sequence.add_edges_from_no_data([(ni, 1) for ni in idle_after.values()]) + + # pylint: disable=unused-argument + @_sequence_instructions.register(SequentialAlignment) + def _sequence_instructions_sequential(self, alignment: SequentialAlignment, sequence: PyDAG): + """Finalize the sequence by adding edges to the DAG, following sequential alignment. + + ``sequence`` is mutated to include all the edges + connecting the elements of the sequence sequentially. + + Nested :class:`.SequenceIR` objects are sequenced recursively. + + Args: + alignment: The IR alignment. + sequence: The graph object to be sequenced. + """ + nodes = sequence.node_indices() + prev = 0 + # The first two nodes are the in\out nodes. + for ind in nodes[2:]: + sequence.add_edge(prev, ind, None) + prev = ind + if isinstance(elem := sequence.get_node_data(ind), SequenceIR): + self._sequence_instructions(elem.alignment, elem.sequence) + sequence.add_edge(prev, 1, None) def __hash__(self): return hash((self.__class__.__name__,)) + + def __eq__(self, other): + return self.__class__.__name__ == other.__class__.__name__ diff --git a/qiskit/pulse/exceptions.py b/qiskit/pulse/exceptions.py index 21bda97ee1b9..42d5330810eb 100644 --- a/qiskit/pulse/exceptions.py +++ b/qiskit/pulse/exceptions.py @@ -27,6 +27,10 @@ def __str__(self): return repr(self.message) +class PulseCompilerError(PulseError): + """Errors raised by the pulse module compiler""" + + class BackendNotSet(PulseError): """Raised if the builder context does not have a backend.""" diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py index 27c00b941b0e..41270ca20383 100644 --- a/qiskit/pulse/transforms/alignments.py +++ b/qiskit/pulse/transforms/alignments.py @@ -13,7 +13,6 @@ from __future__ import annotations import abc from typing import Callable, Tuple -from rustworkx import PyDAG import numpy as np @@ -21,7 +20,6 @@ from qiskit.pulse.exceptions import PulseError from qiskit.pulse.schedule import Schedule, ScheduleComponent from qiskit.pulse.utils import instruction_duration_validation -from qiskit.pulse.model import MixedFrame class AlignmentKind(abc.ABC): @@ -46,22 +44,6 @@ def align(self, schedule: Schedule) -> Schedule: """ pass - @abc.abstractmethod - def set_sequence(self, sequence: PyDAG, **kwargs) -> None: - """Set the sequence of the IR program according to the policy. - - The ``sequence`` is mutated to include all the edges - connecting the elements in the sequence. - - Only top-level elements are sequences. If sub-sequences are nested, - nested sequences are not recursively set. - - Args: - sequence: The graph object to be sequenced. - kwargs: Keyword arguments needed for some subclasses. - """ - pass - @property @abc.abstractmethod def is_sequential(self) -> bool: @@ -108,27 +90,6 @@ def __repr__(self): class SequentialAlignment(AlignmentKind, abc.ABC): """Abstract base class for ``AlignmentKind`` which aligns instructions sequentially""" - def set_sequence(self, sequence: PyDAG, **kwargs) -> None: - """Sets the sequence sequentially. - - The ``sequence`` property of ``ir_program`` is mutated to include all the edges - connecting the elements of the sequence sequentially according to their order. - - Only top-level elements are sequences. If sub-sequences are nested, - nested sequences are not recursively set. - - Args: - sequence: The graph object to be sequenced. - kwargs: Included only to match the signature of the function with other subclasses. - """ - nodes = sequence.node_indices() - prev = 0 - # The first two nodes are the in\out nodes. - for ind in nodes[2:]: - sequence.add_edge(prev, ind, None) - prev = ind - sequence.add_edge(prev, 1, None) - @property def is_sequential(self) -> bool: return True @@ -137,72 +98,6 @@ def is_sequential(self) -> bool: class ParallelAlignment(AlignmentKind, abc.ABC): """Abstract base class for ``AlignmentKind`` which aligns instructions in parallel""" - def set_sequence(self, sequence: PyDAG, **kwargs) -> None: - """Sets the sequence in parallel. - - The ``sequence`` property of ``ir_program`` is mutated to include all the edges - connecting the elements of the sequence in parallel. - - The function requires a ``mixed_frame_mapping`` dictionary - mapping all ``PulseTarget`` - and ``Frame`` to the associated ``MixedFrame`` - to be passed to ``kwargs``. As part - of a pass manager work flow the dictionary is obtained via the pass - :class:`~qiskit.pulse.compiler.MapMixedFrames`. - - Only top-level elements are sequenced. If sub-sequences are nested, - nested sequences are not recursively set. - - Args: - sequence: The graph object to be sequenced. - kwargs: Expecting a keyword argument ``mixed_frame_mapping``. - - Raises: - PulseError: if ``kwargs`` does not include a "mixed_frames_mapping" key. - """ - if "mixed_frames_mapping" not in kwargs.keys() or kwargs["mixed_frames_mapping"] is None: - raise PulseError( - "Expected a keyword argument mixed_frames_mapping with a" - " mapping of PulseTarget and Frame to MixedFrame" - ) - mixed_frame_mapping = kwargs["mixed_frames_mapping"] - - idle_after = {} - for ind in sequence.node_indices(): - if ind in (0, 1): - # In, Out node - continue - node = sequence.get_node_data(ind) - node_mixed_frames = set() - - # if isinstance(node, SequenceIR): - # inst_targets = node.inst_targets - # else: - # inst_targets = [node.inst_target] - if hasattr(node, "inst_targets"): - # SequenceIR object - inst_targets = node.inst_targets - else: - # Instruction object - inst_targets = [node.inst_target] - - for inst_target in inst_targets: - if isinstance(inst_target, MixedFrame): - node_mixed_frames.add(inst_target) - else: - node_mixed_frames |= mixed_frame_mapping[inst_target] - - pred_nodes = [ - idle_after[mixed_frame] - for mixed_frame in node_mixed_frames - if mixed_frame in idle_after - ] - if len(pred_nodes) == 0: - pred_nodes = [0] - for pred_node in pred_nodes: - sequence.add_edge(pred_node, ind, None) - for mixed_frame in node_mixed_frames: - idle_after[mixed_frame] = ind - sequence.add_edges_from_no_data([(ni, 1) for ni in idle_after.values()]) - @property def is_sequential(self) -> bool: return False diff --git a/test/python/pulse/compiler_passes/test_map_mixed_frames.py b/test/python/pulse/compiler_passes/test_map_mixed_frames.py index dcabaeeb1999..a437279d6004 100644 --- a/test/python/pulse/compiler_passes/test_map_mixed_frames.py +++ b/test/python/pulse/compiler_passes/test_map_mixed_frames.py @@ -50,6 +50,7 @@ def test_basic_ir(self): ir_example.append(Play(Constant(100, 0.1), mixed_frame=mf2)) mapping_pass.run(ir_example) + mapping = mapping_pass.property_set["mixed_frames_mapping"] self.assertEqual(len(mapping), 3) self.assertEqual(mapping[mf.pulse_target], {mf, mf2}) self.assertEqual(mapping[mf.frame], {mf}) @@ -104,3 +105,8 @@ def test_with_sub_blocks(self): self.assertEqual(mapping[QubitFrame(0)], {mf1}) self.assertEqual(mapping[QubitFrame(1)], {mf2}) self.assertEqual(mapping[QubitFrame(2)], {mf3}) + + def test_equating(self): + """Test equating of passes""" + self.assertTrue(MapMixedFrame() == MapMixedFrame()) + self.assertFalse(MapMixedFrame() == QubitFrame(1)) diff --git a/test/python/pulse/compiler_passes/test_set_sequence.py b/test/python/pulse/compiler_passes/test_set_sequence.py index 36d27e14cf62..4aba95d3656d 100644 --- a/test/python/pulse/compiler_passes/test_set_sequence.py +++ b/test/python/pulse/compiler_passes/test_set_sequence.py @@ -11,6 +11,7 @@ # that they have been altered from the originals. """Test SetSequence""" +import unittest from test import QiskitTestCase from ddt import ddt, named_data, unpack @@ -34,10 +35,19 @@ AlignEquispaced, ) from qiskit.pulse.compiler import MapMixedFrame, SetSequence -from qiskit.pulse.exceptions import PulseError +from qiskit.pulse.exceptions import PulseCompilerError from .utils import PulseIrTranspiler +class TestSetSequence(QiskitTestCase): + """General tests for set sequence pass""" + + def test_equating(self): + """Test pass equating""" + self.assertTrue(SetSequence() == SetSequence()) + self.assertFalse(SetSequence() == MapMixedFrame()) + + @ddt class TestSetSequenceParallelAlignment(QiskitTestCase): """Test SetSequence pass with Parallel Alignment""" @@ -45,10 +55,7 @@ class TestSetSequenceParallelAlignment(QiskitTestCase): ddt_named_data = [["align_left", AlignLeft()], ["align_right", AlignRight()]] def _get_pm(self) -> PulseIrTranspiler: - pm = PulseIrTranspiler() - pm.append(MapMixedFrame()) - pm.append(SetSequence()) - return pm + return PulseIrTranspiler([MapMixedFrame(), SetSequence()]) @named_data(*ddt_named_data) @unpack @@ -60,41 +67,27 @@ def test_no_mapping_pass_error(self, alignment): ir_example = SequenceIR(alignment) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - with self.assertRaises(PulseError): + with self.assertRaises(PulseCompilerError): pm.run(ir_example) + # TODO: Take care of this weird edge case + @unittest.expectedFailure @named_data(*ddt_named_data) @unpack - def test_single_instruction(self, alignment): - """test with a single instruction""" + def test_instruction_not_in_mapping(self, alignment): + """test with an instruction which is not in the mapping""" ir_example = SequenceIR(alignment) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + ir_example.append(Delay(100, target=Qubit(5))) ir_example = self._get_pm().run(ir_example) edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 2) + self.assertEqual(len(edge_list), 4) self.assertTrue((0, 2) in edge_list) + self.assertTrue((0, 3) in edge_list) self.assertTrue((2, 1) in edge_list) - - # TODO: Take care of this weird edge case - # def test_instruction_not_in_mapping(self): - # """test with an instruction which is not in the mapping""" - # - # ir_example = SequenceIR(AlignLeft()) - # ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - # ir_example.append(Delay(100, target=Qubit(5))) - # - # property_set = {} - # analyze_target_frame_pass(ir_example, property_set) - # ir_example = sequence_pass(ir_example, property_set) - # edge_list = ir_example.sequence.edge_list() - # self.assertEqual(len(edge_list), 4) - # self.assertTrue((0, 2) in edge_list) - # self.assertTrue((0, 3) in edge_list) - # self.assertTrue((2, 1) in edge_list) - # self.assertTrue((3, 1) in edge_list) - # + self.assertTrue((3, 1) in edge_list) @named_data(*ddt_named_data) @unpack @@ -131,9 +124,9 @@ def test_sequential_instructions(self, alignment): @named_data(*ddt_named_data) @unpack - def test_pulse_target_instruction_broadcasting_to_children(self, alignment): - """test with an instruction which is defined on a PulseTarget and is - broadcasted to several children""" + def test_pulse_target_instruction_sequencing_to_dependent_instructions(self, alignment): + """test that an instruction which is defined on a PulseTarget and is sequenced correctly + to several dependent isntructions""" ir_example = SequenceIR(alignment) ir_example.append(Delay(100, target=Qubit(0))) @@ -151,22 +144,26 @@ def test_pulse_target_instruction_broadcasting_to_children(self, alignment): @named_data(*ddt_named_data) @unpack - def test_frame_instruction_broadcasting_to_children(self, alignment): - """test with an instruction which is defined on a Frame and is broadcasted to several children""" + def test_frame_instruction_broadcasting_to_dependent_instructions(self, alignment): + """test that an instruction which is defined on a Frame is correctly sequenced to several + dependent instructions""" ir_example = SequenceIR(alignment) ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) ir_example = self._get_pm().run(ir_example) edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 5) + self.assertEqual(len(edge_list), 7) self.assertTrue((0, 2) in edge_list) self.assertTrue((2, 3) in edge_list) self.assertTrue((2, 4) in edge_list) self.assertTrue((3, 1) in edge_list) self.assertTrue((4, 1) in edge_list) + self.assertTrue((0, 5) in edge_list) + self.assertTrue((5, 1) in edge_list) @named_data(*ddt_named_data) @unpack @@ -305,20 +302,6 @@ class TestSetSequenceSequentialAlignment(QiskitTestCase): ["align_equispaced", AlignEquispaced(100)], ] - @named_data(*ddt_named_data) - @unpack - def test_single_instruction(self, alignment): - """test with a single instruction""" - - ir_example = SequenceIR(alignment) - ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - - ir_example = SetSequence().run(ir_example) - edge_list = ir_example.sequence.edge_list() - self.assertEqual(len(edge_list), 2) - self.assertTrue((0, 2) in edge_list) - self.assertTrue((2, 1) in edge_list) - @named_data(*ddt_named_data) @unpack def test_several_instructions(self, alignment): From 01803ed19c1c9758e34ae2dfe29e6b29ece87d34 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:19:54 +0200 Subject: [PATCH 14/15] add todo --- qiskit/pulse/compiler/passes/set_sequence.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/pulse/compiler/passes/set_sequence.py b/qiskit/pulse/compiler/passes/set_sequence.py index b6118d978d64..745b5b888a96 100644 --- a/qiskit/pulse/compiler/passes/set_sequence.py +++ b/qiskit/pulse/compiler/passes/set_sequence.py @@ -135,6 +135,8 @@ def _sequence_instructions_sequential(self, alignment: SequentialAlignment, sequ """ nodes = sequence.node_indices() prev = 0 + # TODO : What's the correct order to use here? Addition index? Actual index? + # Should at least be documented. # The first two nodes are the in\out nodes. for ind in nodes[2:]: sequence.add_edge(prev, ind, None) From 7cc9186ecda7b32659ba0293491e6248d17edc7e Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Tue, 12 Mar 2024 09:38:43 +0200 Subject: [PATCH 15/15] Corrections. --- .../pulse/compiler/passes/map_mixed_frames.py | 3 ++ qiskit/pulse/compiler/passes/set_sequence.py | 23 ++++----- .../compiler_passes/test_set_sequence.py | 50 +++++++++++++------ 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/qiskit/pulse/compiler/passes/map_mixed_frames.py b/qiskit/pulse/compiler/passes/map_mixed_frames.py index 0170de598d78..5e04342afeeb 100644 --- a/qiskit/pulse/compiler/passes/map_mixed_frames.py +++ b/qiskit/pulse/compiler/passes/map_mixed_frames.py @@ -31,6 +31,9 @@ class MapMixedFrame(AnalysisPass): added dictionary is keyed on every class:`~qiskit.pulse.PulseTaraget` and :class:`~qiskit.pulse.Frame` in :class:`.SequenceIR` with the value being a set of all class:`.MixedFrame`\s associated with the key. + + .. notes:: + The pass will override results of previous ``MapMixedFrame`` runs. """ def __init__(self): diff --git a/qiskit/pulse/compiler/passes/set_sequence.py b/qiskit/pulse/compiler/passes/set_sequence.py index 745b5b888a96..4a021da7f661 100644 --- a/qiskit/pulse/compiler/passes/set_sequence.py +++ b/qiskit/pulse/compiler/passes/set_sequence.py @@ -29,8 +29,9 @@ class SetSequence(TransformationPass): The pass traverses the :class:`.SequenceIR` and recursively sets the sequence, by adding edges to the ``sequence`` property. Sequencing is done according to the alignment strategy. - For parallel alignment types, the pass depends on the results of the analysis pass - :class:`~qiskit.pulse.compiler.passes.MapMixedFrame`. + .. notes:: + The pass depends on the results of the analysis pass + :class:`~qiskit.pulse.compiler.passes.MapMixedFrame`. """ def __init__(self): @@ -45,8 +46,15 @@ def run( Arguments: passmanager_ir: The IR object to be sequenced. - """ + Raises: + PulseCompilerError: if ``property_set`` does not include a mixed_frames_mapping dictionary. + """ + if self.property_set["mixed_frames_mapping"] is None: + raise PulseCompilerError( + "Parallel sequencing requires mixed frames mapping." + " Run MapMixedFrame before sequencing" + ) self._sequence_instructions(passmanager_ir.alignment, passmanager_ir.sequence) return passmanager_ir @@ -74,16 +82,7 @@ def _sequence_instructions_parallel(self, alignment: ParallelAlignment, sequence Args: alignment: The IR alignment. sequence: The graph object to be sequenced. - - Raises: - PulseCompilerError: if ``property_set`` does not include a mixed_frames_mapping dictionary. """ - if self.property_set["mixed_frames_mapping"] is None: - raise PulseCompilerError( - "Parallel sequencing requires mixed frames mapping." - " Run MapMixedFrame before sequencing" - ) - mixed_frame_mapping = self.property_set["mixed_frames_mapping"] idle_after = {} diff --git a/test/python/pulse/compiler_passes/test_set_sequence.py b/test/python/pulse/compiler_passes/test_set_sequence.py index 4aba95d3656d..c6aac4d5d143 100644 --- a/test/python/pulse/compiler_passes/test_set_sequence.py +++ b/test/python/pulse/compiler_passes/test_set_sequence.py @@ -54,8 +54,9 @@ class TestSetSequenceParallelAlignment(QiskitTestCase): ddt_named_data = [["align_left", AlignLeft()], ["align_right", AlignRight()]] - def _get_pm(self) -> PulseIrTranspiler: - return PulseIrTranspiler([MapMixedFrame(), SetSequence()]) + def setUp(self): + super().setUp() + self._pm = PulseIrTranspiler([MapMixedFrame(), SetSequence()]) @named_data(*ddt_named_data) @unpack @@ -81,7 +82,7 @@ def test_instruction_not_in_mapping(self, alignment): ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Delay(100, target=Qubit(5))) - ir_example = self._get_pm().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 4) self.assertTrue((0, 2) in edge_list) @@ -98,7 +99,7 @@ def test_parallel_instructions(self, alignment): ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - ir_example = self._get_pm().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 4) self.assertTrue((0, 2) in edge_list) @@ -115,7 +116,7 @@ def test_sequential_instructions(self, alignment): ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) - ir_example = self._get_pm().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 3) self.assertTrue((0, 2) in edge_list) @@ -133,7 +134,7 @@ def test_pulse_target_instruction_sequencing_to_dependent_instructions(self, ali ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) - ir_example = self._get_pm().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 5) self.assertTrue((0, 2) in edge_list) @@ -154,7 +155,7 @@ def test_frame_instruction_broadcasting_to_dependent_instructions(self, alignmen ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - ir_example = self._get_pm().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 7) self.assertTrue((0, 2) in edge_list) @@ -176,7 +177,7 @@ def test_pulse_target_instruction_dependency(self, alignment): ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) ir_example.append(Delay(100, target=Qubit(0))) - ir_example = self._get_pm().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 5) self.assertTrue((0, 2) in edge_list) @@ -195,7 +196,7 @@ def test_frame_instruction_dependency(self, alignment): ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(0)))) ir_example.append(ShiftPhase(100, frame=QubitFrame(0))) - ir_example = self._get_pm().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 5) self.assertTrue((0, 2) in edge_list) @@ -216,7 +217,7 @@ def test_recursion_to_sub_blocks(self, alignment): ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) - ir_example = self._get_pm().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list_sub_block = ir_example.elements()[1].sequence.edge_list() self.assertEqual(len(edge_list_sub_block), 2) self.assertTrue((0, 2) in edge_list_sub_block) @@ -234,7 +235,7 @@ def test_with_parallel_sub_block(self, alignment): ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) - ir_example = self._get_pm().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 4) self.assertTrue((0, 2) in edge_list) @@ -255,7 +256,7 @@ def test_with_simple_sequential_sub_block(self, alignment): ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) ir_example.append(sub_block) - ir_example = self._get_pm().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 5) self.assertTrue((0, 2) in edge_list) @@ -279,7 +280,7 @@ def test_with_sequential_sub_block_with_more_dependencies(self, alignment): ir_example.append(sub_block) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) - ir_example = self._get_pm().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 8) self.assertTrue((0, 2) in edge_list) @@ -302,6 +303,23 @@ class TestSetSequenceSequentialAlignment(QiskitTestCase): ["align_equispaced", AlignEquispaced(100)], ] + def setUp(self): + super().setUp() + self._pm = PulseIrTranspiler([MapMixedFrame(), SetSequence()]) + + @named_data(*ddt_named_data) + @unpack + def test_no_mapping_pass_error(self, alignment): + """test that running without MapMixedFrame pass raises a PulseError""" + + pm = PulseIrTranspiler() + pm.append(SetSequence()) + ir_example = SequenceIR(alignment) + ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(1)))) + + with self.assertRaises(PulseCompilerError): + pm.run(ir_example) + @named_data(*ddt_named_data) @unpack def test_several_instructions(self, alignment): @@ -312,7 +330,7 @@ def test_several_instructions(self, alignment): ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(1), QubitFrame(1)))) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(2), QubitFrame(1)))) - ir_example = SetSequence().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 4) self.assertTrue((0, 2) in edge_list) @@ -332,7 +350,7 @@ def test_recursion_to_sub_blocks(self, alignment): ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(0)))) ir_example.append(sub_block) - ir_example = SetSequence().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list_sub_block = ir_example.elements()[1].sequence.edge_list() self.assertEqual(len(edge_list_sub_block), 2) @@ -352,7 +370,7 @@ def test_sub_blocks_and_instructions(self, alignment): ir_example.append(sub_block) ir_example.append(Play(Constant(100, 0.1), mixed_frame=MixedFrame(Qubit(0), QubitFrame(2)))) - ir_example = SetSequence().run(ir_example) + ir_example = self._pm.run(ir_example) edge_list = ir_example.sequence.edge_list() self.assertEqual(len(edge_list), 4)