diff --git a/qiskit/pulse/compiler/__init__.py b/qiskit/pulse/compiler/__init__.py index 4ff0663e4209..c3b01b0ca8ed 100644 --- a/qiskit/pulse/compiler/__init__.py +++ b/qiskit/pulse/compiler/__init__.py @@ -13,4 +13,4 @@ """Pass-based Qiskit pulse program compiler.""" from .passmanager import BlockTranspiler, BlockToIrCompiler -from .passes import MapMixedFrame, SetSequence, SetSchedule +from .passes import MapMixedFrame, SetSequence, SetSchedule, Flatten diff --git a/qiskit/pulse/compiler/passes/__init__.py b/qiskit/pulse/compiler/passes/__init__.py index ff98df134430..28d6dcf8f467 100644 --- a/qiskit/pulse/compiler/passes/__init__.py +++ b/qiskit/pulse/compiler/passes/__init__.py @@ -15,3 +15,4 @@ from .map_mixed_frames import MapMixedFrame from .set_sequence import SetSequence from .schedule import SetSchedule +from .flatten import Flatten diff --git a/qiskit/pulse/compiler/passes/flatten.py b/qiskit/pulse/compiler/passes/flatten.py new file mode 100644 index 000000000000..6c0f2d75c645 --- /dev/null +++ b/qiskit/pulse/compiler/passes/flatten.py @@ -0,0 +1,93 @@ +# 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 flattening pass for Qiskit PulseIR compilation.""" + +from __future__ import annotations + +from qiskit.pulse.compiler.basepasses import TransformationPass +from qiskit.pulse.ir import SequenceIR +from qiskit.pulse.exceptions import PulseCompilerError + + +class Flatten(TransformationPass): + """Flatten ``SequenceIR`` object. + + The flattening process includes breaking up nested IRs until only instructions remain. + After flattening the object will contain all instructions, timing information, and the + complete sequence graph. However, the alignment of nested IRs will be lost. Because alignment + information is essential for scheduling, flattening an unscheduled IR is not allowed. + One should apply :class:`~qiskit.pulse.compiler.passes.SetSchedule` first. + """ + + def __init__(self): + """Create new Flatten pass""" + super().__init__(target=None) + + def run( + self, + passmanager_ir: SequenceIR, + ) -> SequenceIR: + """Run the pass.""" + + self._flatten_recursive(passmanager_ir) + return passmanager_ir + + # pylint: disable=cell-var-from-loop + def _flatten_recursive(self, prog: SequenceIR) -> SequenceIR: + """Recursively flatten the SequenceIR. + + Returns: + A flattened ``SequenceIR`` object. + + Raises: + PulseCompilerError: If ``prog`` is not scheduled. + """ + # TODO : Consider replacing the alignment to "NullAlignment", as the original alignment + # has no meaning. + + def edge_map(_x, _y, _node): + if _y == _node: + return 0 + if _x == _node: + return 1 + return None + + if any(prog.time_table[x] is None for x in prog.sequence.node_indices() if x not in (0, 1)): + raise PulseCompilerError( + "Can not flatten unscheduled IR. Use SetSchedule pass before Flatten." + ) + + for ind in prog.sequence.node_indices(): + if isinstance(sub_block := prog.sequence.get_node_data(ind), SequenceIR): + self._flatten_recursive(sub_block) + initial_time = prog.time_table[ind] + nodes_mapping = prog.sequence.substitute_node_with_subgraph( + ind, sub_block.sequence, lambda x, y, _: edge_map(x, y, ind) + ) + for old_node in nodes_mapping.keys(): + if old_node not in (0, 1): + prog.time_table[nodes_mapping[old_node]] = ( + initial_time + sub_block.time_table[old_node] + ) + + del prog.time_table[ind] + prog.sequence.remove_node_retain_edges(nodes_mapping[0]) + prog.sequence.remove_node_retain_edges(nodes_mapping[1]) + + return prog + + def __hash__(self): + return hash((self.__class__.__name__,)) + + def __eq__(self, other): + return self.__class__.__name__ == other.__class__.__name__ diff --git a/qiskit/pulse/ir/ir.py b/qiskit/pulse/ir/ir.py index f2c28efd0e4c..538d2931f7f6 100644 --- a/qiskit/pulse/ir/ir.py +++ b/qiskit/pulse/ir/ir.py @@ -197,12 +197,8 @@ def duration(self) -> int | None: except TypeError: return None - def draw(self, recursive: bool = False): + def draw(self): """Draw the graph of the SequenceIR""" - if recursive: - draw_sequence = self.flatten().sequence - else: - draw_sequence = self.sequence def _draw_nodes(n): if n is SequenceIR._InNode or n is SequenceIR._OutNode: @@ -214,66 +210,10 @@ def _draw_nodes(n): return {"label": f"{n.__class__.__name__}" + name} return graphviz_draw( - draw_sequence, + self.sequence, node_attr_fn=_draw_nodes, ) - # pylint: disable=cell-var-from-loop - def flatten(self, inplace: bool = False) -> SequenceIR: - """Recursively flatten the SequenceIR. - - The flattening process includes breaking up nested IRs until only instructions remain. - The flattened object will contain all instructions, timing information, and the - complete sequence graph. However, the alignment of nested IRs will be lost. Because of - this, flattening an unscheduled IR is not allowed. - - Args: - inplace: If ``True`` flatten the object itself. If ``False`` return a flattened copy. - - Returns: - A flattened ``SequenceIR`` object. - - Raises: - PulseError: If the IR (or nested IRs) are not scheduled. - """ - # TODO : Verify that the block\sub blocks are sequenced correctly. - if inplace: - block = self - else: - block = self.copy() - - def edge_map(_x, _y, _node): - if _y == _node: - return 0 - if _x == _node: - return 1 - return None - - if any( - block.time_table[x] is None for x in block.sequence.node_indices() if x not in (0, 1) - ): - raise PulseError("Can not flatten unscheduled IR") - - for ind in block.sequence.node_indices(): - 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( - 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(): - if old_node not in (0, 1): - 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 copy(self) -> SequenceIR: """Create a copy of ``SequenceIR``. diff --git a/test/python/pulse/compiler_passes/test_flatten.py b/test/python/pulse/compiler_passes/test_flatten.py new file mode 100644 index 000000000000..a5dfb6c62702 --- /dev/null +++ b/test/python/pulse/compiler_passes/test_flatten.py @@ -0,0 +1,222 @@ +# 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 Schedule""" +import copy + +from test import QiskitTestCase + +from qiskit.pulse import ( + Constant, + Play, +) + +from qiskit.pulse.ir import ( + SequenceIR, +) + +from qiskit.pulse.model import QubitFrame, Qubit +from qiskit.pulse.transforms import ( + AlignLeft, +) +from qiskit.pulse.compiler import MapMixedFrame, SetSequence, SetSchedule, Flatten +from qiskit.pulse.exceptions import PulseCompilerError +from .utils import PulseIrTranspiler + + +class TestFlatten(QiskitTestCase): + """Flatten tests""" + + def setUp(self): + super().setUp() + self._schedule_pm = PulseIrTranspiler([MapMixedFrame(), SetSequence(), SetSchedule()]) + self._flatten = Flatten() + + def _compare_scheduled_elements(self, list1, list2): + if len(list1) != len(list2): + return False + for x in list1: + if x not in list2: + return False + return 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) + + ir_example = self._schedule_pm.run(ir_example) + ref = copy.deepcopy(ir_example) + ir_example = self._flatten.run(ir_example) + + self.assertEqual(ref, 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) + + ir_example = self._schedule_pm.run(ir_example) + flat = self._flatten.run(ir_example) + 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) + self.assertTrue((0, inst) in flat.scheduled_elements()) + self.assertTrue((0, inst2) in flat.scheduled_elements()) + + 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))) + + ir_example = self._schedule_pm.run(ir_example) + flat = self._flatten.run(ir_example) + + 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) + self.assertTrue( + self._compare_scheduled_elements( + ir_example.scheduled_elements(recursive=True), flat.scheduled_elements() + ) + ) + + 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))) + + ir_example = self._schedule_pm.run(ir_example) + flat = self._flatten.run(ir_example) + + 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) + self.assertTrue( + self._compare_scheduled_elements( + ir_example.scheduled_elements(recursive=True), flat.scheduled_elements() + ) + ) + + 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) + + ir_example = self._schedule_pm.run(ir_example) + flat = self._flatten.run(ir_example) + ref = SequenceIR(AlignLeft()) + ref.append(inst1) + ref.append(inst2) + + ref = self._schedule_pm.run(ref) + + self.assertEqual(flat, ref) + self.assertTrue( + self._compare_scheduled_elements( + ir_example.scheduled_elements(recursive=True), flat.scheduled_elements() + ) + ) + + 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) + + ir_example = self._schedule_pm.run(ir_example) + flat = self._flatten.run(ir_example) + 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)) + # Verify that nodes removed from the graph are also removed from _time_table. + self.assertEqual( + {x for x in flat.sequence.node_indices() if x not in (0, 1)}, flat._time_table.keys() + ) + self.assertTrue( + self._compare_scheduled_elements( + ir_example.scheduled_elements(recursive=True), flat.scheduled_elements() + ) + ) + + def test_flatten_not_scheduled(self): + """Test that flattening an unscheduled IR raises an error""" + 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) + + with self.assertRaises(PulseCompilerError): + self._flatten.run(ir_example) diff --git a/test/python/pulse/test_pulse_ir.py b/test/python/pulse/test_pulse_ir.py index 0bcf2f926aa9..9fba26b89f49 100644 --- a/test/python/pulse/test_pulse_ir.py +++ b/test/python/pulse/test_pulse_ir.py @@ -14,7 +14,6 @@ import copy from test import QiskitTestCase -from test.python.pulse.compiler_passes.utils import PulseIrTranspiler from rustworkx import is_isomorphic_node_match from qiskit.pulse import ( @@ -31,27 +30,11 @@ from qiskit.pulse.transforms import AlignLeft, AlignRight from qiskit.pulse.model import QubitFrame, Qubit, MixedFrame from qiskit.pulse.exceptions import PulseError -from qiskit.pulse.compiler import MapMixedFrame, SetSequence, SetSchedule class TestSequenceIR(QiskitTestCase): """Test SequenceIR objects""" - def _get_scheduling_pm(self) -> PulseIrTranspiler: - pm = PulseIrTranspiler() - pm.append(MapMixedFrame()) - pm.append(SetSequence()) - pm.append(SetSchedule()) - return pm - - def _compare_scheduled_elements(self, list1, list2): - if len(list1) != len(list2): - return False - for x in list1: - if x not in list2: - return False - return True - def test_ir_creation(self): """Test ir creation""" ir_example = SequenceIR(AlignLeft()) @@ -278,192 +261,6 @@ 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) - - ir_example = self._get_scheduling_pm().run(ir_example) - - 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) - - ir_example = self._get_scheduling_pm().run(ir_example) - - 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) - - ir_example = self._get_scheduling_pm().run(ir_example) - - 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) - self.assertTrue((0, inst) in flat.scheduled_elements()) - self.assertTrue((0, inst2) in flat.scheduled_elements()) - - 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))) - - ir_example = self._get_scheduling_pm().run(ir_example) - - 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) - self.assertTrue( - self._compare_scheduled_elements( - ir_example.scheduled_elements(recursive=True), flat.scheduled_elements() - ) - ) - - 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))) - - ir_example = self._get_scheduling_pm().run(ir_example) - - 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) - self.assertTrue( - self._compare_scheduled_elements( - ir_example.scheduled_elements(recursive=True), flat.scheduled_elements() - ) - ) - - 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) - - ir_example = self._get_scheduling_pm().run(ir_example) - - flat = ir_example.flatten() - ref = SequenceIR(AlignLeft()) - ref.append(inst1) - ref.append(inst2) - - ref = self._get_scheduling_pm().run(ref) - - self.assertEqual(flat, ref) - self.assertTrue( - self._compare_scheduled_elements( - ir_example.scheduled_elements(recursive=True), flat.scheduled_elements() - ) - ) - - 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) - - ir_example = self._get_scheduling_pm().run(ir_example) - - 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)) - # Verify that nodes removed from the graph are also removed from _time_table. - self.assertEqual( - {x for x in flat.sequence.node_indices() if x not in (0, 1)}, flat._time_table.keys() - ) - self.assertTrue( - self._compare_scheduled_elements( - ir_example.scheduled_elements(recursive=True), flat.scheduled_elements() - ) - ) - - def test_flatten_not_scheduled(self): - """Test that flattening an unscheduled IR raises an error""" - 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) - - with self.assertRaises(PulseError): - ir_example.flatten() - def test_ir_equating_different_alignment(self): """Test equating of blocks with different alignment""" self.assertFalse(SequenceIR(AlignLeft()) == SequenceIR(AlignRight()))