From d4ee6ef36702eadf6682af087643d1ac3e9cb160 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 8 Feb 2024 05:10:12 +0900 Subject: [PATCH 1/5] WIP pulse compiler skeleton --- qiskit/pulse/compiler/__init__.py | 15 ++++ qiskit/pulse/compiler/basepasses.py | 72 +++++++++++++++ qiskit/pulse/compiler/converters.py | 42 +++++++++ qiskit/pulse/compiler/passes/__init__.py | 13 +++ qiskit/pulse/compiler/passmanager.py | 110 +++++++++++++++++++++++ test/python/pulse/_dummy_programs.py | 34 +++++++ test/python/pulse/test_compile.py | 31 +++++++ 7 files changed, 317 insertions(+) create mode 100644 qiskit/pulse/compiler/__init__.py create mode 100644 qiskit/pulse/compiler/basepasses.py create mode 100644 qiskit/pulse/compiler/converters.py create mode 100644 qiskit/pulse/compiler/passes/__init__.py create mode 100644 qiskit/pulse/compiler/passmanager.py create mode 100644 test/python/pulse/_dummy_programs.py create mode 100644 test/python/pulse/test_compile.py diff --git a/qiskit/pulse/compiler/__init__.py b/qiskit/pulse/compiler/__init__.py new file mode 100644 index 000000000000..839845d145e5 --- /dev/null +++ b/qiskit/pulse/compiler/__init__.py @@ -0,0 +1,15 @@ +# 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. + +"""Pass-based Qiskit pulse program compiler.""" + +from .passmanager import PulsePassManager diff --git a/qiskit/pulse/compiler/basepasses.py b/qiskit/pulse/compiler/basepasses.py new file mode 100644 index 000000000000..4a9e58cb8603 --- /dev/null +++ b/qiskit/pulse/compiler/basepasses.py @@ -0,0 +1,72 @@ +# 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 abc import ABC +from qiskit.passmanager.base_tasks import GenericPass +from qiskit.transpiler.target import Target + +PulseIR = object + + +class TransformationPass(GenericPass, ABC): + """A base transform pass for Qiskit PulseIR. + + A transform pass modifies the input Qiskit PulseIR and returns an updated PulseIR. + The returned object can be new instance, or the pass can mutate and return the same object. + """ + + def __init__( + self, + target: Target, + ): + """Create new transform pass. + + Args: + target: System configuration information presented in the form of Qiskit model. + """ + super().__init__() + self.target = target + + def run( + self, + passmanager_ir: PulseIR, + ) -> PulseIR: + raise NotImplementedError + + +class AnalysisPass(GenericPass, ABC): + """A base analysis pass for Qiskit PulseIR. + + An analysis pass performs investigation on the input Qiskit PulseIR. + The information obtained may be stored in the property set. + This pass returns nothing. + """ + + def __init__( + self, + target: Target, + ): + """Create new transform pass. + + Args: + target: System configuration information presented in the form of Qiskit model. + """ + super().__init__() + self.target = target + + def run( + self, + passmanager_ir: PulseIR, + ) -> None: + raise NotImplementedError diff --git a/qiskit/pulse/compiler/converters.py b/qiskit/pulse/compiler/converters.py new file mode 100644 index 000000000000..1e6e57a1888e --- /dev/null +++ b/qiskit/pulse/compiler/converters.py @@ -0,0 +1,42 @@ +# 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 qiskit.pulse.schedule import ScheduleBlock + +PulseIR = object + + +def schedule_to_ir(schedule: ScheduleBlock) -> PulseIR: + """Convert ScheduleBlock into PulseIR. + + Args: + schedule: Schedule to convert. + + Returns: + PulseIR used internally in the pulse compiler. + """ + raise NotImplementedError + + +def ir_to_schedule(pulse_ir: PulseIR) -> ScheduleBlock: + """Convert PulseIR to ScheduleBlock. + + Args: + pulse_ir: PulseIR to convert. + + Returns: + ScheduleBlock that end-user may interact with. + """ + raise NotImplementedError diff --git a/qiskit/pulse/compiler/passes/__init__.py b/qiskit/pulse/compiler/passes/__init__.py new file mode 100644 index 000000000000..d0807e55c63d --- /dev/null +++ b/qiskit/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. + +"""Built-in pulse compile passes.""" diff --git a/qiskit/pulse/compiler/passmanager.py b/qiskit/pulse/compiler/passmanager.py new file mode 100644 index 000000000000..ec66ab55befd --- /dev/null +++ b/qiskit/pulse/compiler/passmanager.py @@ -0,0 +1,110 @@ +# 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. + +"""Pass manager for pulse schedules.""" + +from __future__ import annotations + +from collections.abc import Callable +from typing import Any + +from qiskit.passmanager import BasePassManager +from qiskit.pulse.compiler.converters import schedule_to_ir, ir_to_schedule +from qiskit.pulse.schedule import ScheduleBlock + +# TODO replace with actual type +PulseIR = object + + +class PulsePassManager(BasePassManager): + """A pass manager for compiling Qiskit ScheduleBlock programs.""" + + def _passmanager_frontend( + self, + input_program: ScheduleBlock, + **kwargs, + ) -> PulseIR: + return schedule_to_ir(input_program) + + def _passmanager_backend( + self, + passmanager_ir: PulseIR, + in_program: ScheduleBlock, + **kwargs, + ) -> Any: + output = kwargs.get("output", "schedule_block") + + if output == "pulse_ir": + return passmanager_ir + if output == "schedule_block": + return ir_to_schedule(passmanager_ir) + + raise ValueError(f"Specified target format '{output}' is not supported.") + + # pylint: disable=arguments-differ + def run( + self, + schedules: ScheduleBlock | list[ScheduleBlock], + output: str = "schedule_block", + callback: Callable | None = None, + num_processes: int | None = None, + ) -> Any: + """Run all the passes on the input pulse schedules. + + Args: + schedules: Input pulse programs to transform via all the registered passes. + When a list of schedules are passed, the transform is performed in parallel + for each input schedule with multiprocessing. + output: Format of the output program:: + + schedule_block: Return in :class:`.ScheduleBlock` format. + pulse_ir: Return in :class:`.PulseIR` format. + + callback: A callback function that will be called after each pass execution. The + function will be called with 4 keyword arguments:: + + task (GenericPass): the pass being run + passmanager_ir (Any): depending on pass manager subclass + property_set (PropertySet): the property set + running_time (float): the time to execute the pass + count (int): the index for the pass execution + + The exact arguments pass expose the internals of the pass + manager and are subject to change as the pass manager internals + change. If you intend to reuse a callback function over + multiple releases be sure to check that the arguments being + passed are the same. + + To use the callback feature you define a function that will + take in kwargs dict and access the variables. For example:: + + def callback_func(**kwargs): + task = kwargs['task'] + passmanager_ir = kwargs['passmanager_ir'] + property_set = kwargs['property_set'] + running_time = kwargs['running_time'] + count = kwargs['count'] + ... + num_processes: The maximum number of parallel processes to launch if parallel + execution is enabled. This argument overrides ``num_processes`` in the user + configuration file, and the ``QISKIT_NUM_PROCS`` environment variable. If set + to ``None`` the system default or local user configuration will be used. + + Returns: + The transformed program(s) in specified program format. + """ + return super().run( + in_programs=schedules, + callback=callback, + num_processes=num_processes, + output=output, + ) diff --git a/test/python/pulse/_dummy_programs.py b/test/python/pulse/_dummy_programs.py new file mode 100644 index 000000000000..1c26bf44f5e4 --- /dev/null +++ b/test/python/pulse/_dummy_programs.py @@ -0,0 +1,34 @@ +# 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 pulse schedule library for unit tests.""" + +from qiskit.pulse import builder +from qiskit.pulse.library import symbolic_pulses +from qiskit.pulse import channels +from qiskit.pulse.schedule import ScheduleBlock + + +def play_gaussian() -> ScheduleBlock: + """Create simple schedule of playing a single Gaussian pulse.""" + + with builder.build() as out: + builder.play( + pulse=symbolic_pulses.Gaussian( + duration=100, + amp=0.1, + sigma=25, + angle=0.0, + ), + channel=channels.DriveChannel(0), + ) + return out diff --git a/test/python/pulse/test_compile.py b/test/python/pulse/test_compile.py new file mode 100644 index 000000000000..f76809fff342 --- /dev/null +++ b/test/python/pulse/test_compile.py @@ -0,0 +1,31 @@ +# 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 test cases for pulse compilation.""" + +from qiskit.pulse.compiler import PulsePassManager + +from test import QiskitTestCase # pylint: disable=wrong-import-order +from . import _dummy_programs as schedule_lib + + +class TestCompiler(QiskitTestCase): + """Test case for pulse compiler.""" + + def test_roundtrip(self): + """Test just returns the input program.""" + # Just convert an input to PulseIR and convert it back to ScheduleBlock. + pm = PulsePassManager() + + in_prog = schedule_lib.play_gaussian() + out_prog = pm.run(schedules=in_prog, output="schedule_block") + self.assertEqual(in_prog, out_prog) From ede5d49145c720c3be36ac066502dc1f9a84f484 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 22 Feb 2024 01:11:18 +0900 Subject: [PATCH 2/5] Addressed comments --- qiskit/pulse/compiler/basepasses.py | 8 +++++--- qiskit/pulse/compiler/passmanager.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/qiskit/pulse/compiler/basepasses.py b/qiskit/pulse/compiler/basepasses.py index 4a9e58cb8603..bdceac2ff987 100644 --- a/qiskit/pulse/compiler/basepasses.py +++ b/qiskit/pulse/compiler/basepasses.py @@ -12,7 +12,7 @@ """A base pass for Qiskit PulseIR compilation.""" -from abc import ABC +from abc import ABC, abstractmethod from qiskit.passmanager.base_tasks import GenericPass from qiskit.transpiler.target import Target @@ -38,11 +38,12 @@ def __init__( super().__init__() self.target = target + @abstractmethod def run( self, passmanager_ir: PulseIR, ) -> PulseIR: - raise NotImplementedError + pass class AnalysisPass(GenericPass, ABC): @@ -65,8 +66,9 @@ def __init__( super().__init__() self.target = target + @abstractmethod def run( self, passmanager_ir: PulseIR, ) -> None: - raise NotImplementedError + pass diff --git a/qiskit/pulse/compiler/passmanager.py b/qiskit/pulse/compiler/passmanager.py index ec66ab55befd..09a9d9752475 100644 --- a/qiskit/pulse/compiler/passmanager.py +++ b/qiskit/pulse/compiler/passmanager.py @@ -70,7 +70,7 @@ def run( pulse_ir: Return in :class:`.PulseIR` format. callback: A callback function that will be called after each pass execution. The - function will be called with 4 keyword arguments:: + function will be called with 5 keyword arguments:: task (GenericPass): the pass being run passmanager_ir (Any): depending on pass manager subclass From 3499eb8f4e31ddf8b37b7be1e94db94f4ea8d474 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 22 Feb 2024 01:16:18 +0900 Subject: [PATCH 3/5] Added gentle warning to eq check --- qiskit/pulse/compiler/basepasses.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/qiskit/pulse/compiler/basepasses.py b/qiskit/pulse/compiler/basepasses.py index bdceac2ff987..cb62282d165d 100644 --- a/qiskit/pulse/compiler/basepasses.py +++ b/qiskit/pulse/compiler/basepasses.py @@ -12,7 +12,9 @@ """A base pass for Qiskit PulseIR compilation.""" +import warnings from abc import ABC, abstractmethod + from qiskit.passmanager.base_tasks import GenericPass from qiskit.transpiler.target import Target @@ -45,6 +47,15 @@ def run( ) -> PulseIR: pass + def __eq__(self, other): + warnings.warn( + f"{self.__class__} does not explicitly define a protocol to evaluate equality. " + "Two pass objects instantiated individually with the same configuration may be " + "considered as different passes.", + RuntimeWarning, + ) + super().__eq__(other) + class AnalysisPass(GenericPass, ABC): """A base analysis pass for Qiskit PulseIR. @@ -72,3 +83,12 @@ def run( passmanager_ir: PulseIR, ) -> None: pass + + def __eq__(self, other): + warnings.warn( + f"{self.__class__} does not explicitly define a protocol to evaluate equality. " + "Two pass objects instantiated individually with the same configuration may be " + "considered as different passes.", + RuntimeWarning, + ) + super().__eq__(other) From 214884a76f1284835884b3a8dae6ce25e97ebf01 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 22 Feb 2024 01:45:54 +0900 Subject: [PATCH 4/5] Replace type with actual IrBlock and supported minimum test --- qiskit/pulse/compiler/basepasses.py | 9 ++++---- qiskit/pulse/compiler/converters.py | 32 ++++++++++++++++++++++------ qiskit/pulse/compiler/passmanager.py | 8 +++---- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/qiskit/pulse/compiler/basepasses.py b/qiskit/pulse/compiler/basepasses.py index cb62282d165d..8e1c40443ae7 100644 --- a/qiskit/pulse/compiler/basepasses.py +++ b/qiskit/pulse/compiler/basepasses.py @@ -17,8 +17,7 @@ from qiskit.passmanager.base_tasks import GenericPass from qiskit.transpiler.target import Target - -PulseIR = object +from qiskit.pulse.ir import IrBlock class TransformationPass(GenericPass, ABC): @@ -43,8 +42,8 @@ def __init__( @abstractmethod def run( self, - passmanager_ir: PulseIR, - ) -> PulseIR: + passmanager_ir: IrBlock, + ) -> IrBlock: pass def __eq__(self, other): @@ -80,7 +79,7 @@ def __init__( @abstractmethod def run( self, - passmanager_ir: PulseIR, + passmanager_ir: IrBlock, ) -> None: pass diff --git a/qiskit/pulse/compiler/converters.py b/qiskit/pulse/compiler/converters.py index 1e6e57a1888e..a4318906067e 100644 --- a/qiskit/pulse/compiler/converters.py +++ b/qiskit/pulse/compiler/converters.py @@ -14,11 +14,10 @@ from qiskit.pulse.schedule import ScheduleBlock +from qiskit.pulse.ir import IrBlock, IrInstruction -PulseIR = object - -def schedule_to_ir(schedule: ScheduleBlock) -> PulseIR: +def schedule_to_ir(schedule: ScheduleBlock) -> IrBlock: """Convert ScheduleBlock into PulseIR. Args: @@ -27,10 +26,20 @@ def schedule_to_ir(schedule: ScheduleBlock) -> PulseIR: Returns: PulseIR used internally in the pulse compiler. """ - raise NotImplementedError + out = IrBlock(alignment=schedule.alignment_context) + + def _wrap_recursive(_elm): + if isinstance(_elm, ScheduleBlock): + return schedule_to_ir(_elm) + return IrInstruction(instruction=_elm) + + for element in schedule.blocks: + wrapped_element = _wrap_recursive(element) + out.add_element(wrapped_element) + return out -def ir_to_schedule(pulse_ir: PulseIR) -> ScheduleBlock: +def ir_to_schedule(pulse_ir: IrBlock) -> ScheduleBlock: """Convert PulseIR to ScheduleBlock. Args: @@ -39,4 +48,15 @@ def ir_to_schedule(pulse_ir: PulseIR) -> ScheduleBlock: Returns: ScheduleBlock that end-user may interact with. """ - raise NotImplementedError + out = ScheduleBlock(alignment_context=pulse_ir.alignment) + + def _unwrap_recursive(_elm): + if isinstance(_elm, IrBlock): + return ir_to_schedule(_elm) + return _elm.instruction + + for element in pulse_ir.elements: + unwrapped_element = _unwrap_recursive(element) + out.append(unwrapped_element, inplace=True) + + return out diff --git a/qiskit/pulse/compiler/passmanager.py b/qiskit/pulse/compiler/passmanager.py index 09a9d9752475..1eab9b0cb525 100644 --- a/qiskit/pulse/compiler/passmanager.py +++ b/qiskit/pulse/compiler/passmanager.py @@ -18,12 +18,10 @@ from typing import Any from qiskit.passmanager import BasePassManager +from qiskit.pulse.ir import IrBlock from qiskit.pulse.compiler.converters import schedule_to_ir, ir_to_schedule from qiskit.pulse.schedule import ScheduleBlock -# TODO replace with actual type -PulseIR = object - class PulsePassManager(BasePassManager): """A pass manager for compiling Qiskit ScheduleBlock programs.""" @@ -32,12 +30,12 @@ def _passmanager_frontend( self, input_program: ScheduleBlock, **kwargs, - ) -> PulseIR: + ) -> IrBlock: return schedule_to_ir(input_program) def _passmanager_backend( self, - passmanager_ir: PulseIR, + passmanager_ir: IrBlock, in_program: ScheduleBlock, **kwargs, ) -> Any: From 3c30d97ec7aa82d82a50cf43cc3c1ab1456c5267 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 28 Feb 2024 18:20:27 +0900 Subject: [PATCH 5/5] Cleanup. Removed output argument and added compiler subclasses. Integrate converters into compiler itself. --- qiskit/pulse/compiler/__init__.py | 2 +- qiskit/pulse/compiler/converters.py | 62 -------------- qiskit/pulse/compiler/passmanager.py | 117 +++++++++++++++++++-------- test/python/pulse/_dummy_programs.py | 31 +++++++ test/python/pulse/test_compile.py | 17 +++- 5 files changed, 130 insertions(+), 99 deletions(-) delete mode 100644 qiskit/pulse/compiler/converters.py diff --git a/qiskit/pulse/compiler/__init__.py b/qiskit/pulse/compiler/__init__.py index 839845d145e5..e8bd600b1a05 100644 --- a/qiskit/pulse/compiler/__init__.py +++ b/qiskit/pulse/compiler/__init__.py @@ -12,4 +12,4 @@ """Pass-based Qiskit pulse program compiler.""" -from .passmanager import PulsePassManager +from .passmanager import BlockTranspiler, BlockToIrCompiler diff --git a/qiskit/pulse/compiler/converters.py b/qiskit/pulse/compiler/converters.py deleted file mode 100644 index a4318906067e..000000000000 --- a/qiskit/pulse/compiler/converters.py +++ /dev/null @@ -1,62 +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. - -"""A base pass for Qiskit PulseIR compilation.""" - - -from qiskit.pulse.schedule import ScheduleBlock -from qiskit.pulse.ir import IrBlock, IrInstruction - - -def schedule_to_ir(schedule: ScheduleBlock) -> IrBlock: - """Convert ScheduleBlock into PulseIR. - - Args: - schedule: Schedule to convert. - - Returns: - PulseIR used internally in the pulse compiler. - """ - out = IrBlock(alignment=schedule.alignment_context) - - def _wrap_recursive(_elm): - if isinstance(_elm, ScheduleBlock): - return schedule_to_ir(_elm) - return IrInstruction(instruction=_elm) - - for element in schedule.blocks: - wrapped_element = _wrap_recursive(element) - out.add_element(wrapped_element) - return out - - -def ir_to_schedule(pulse_ir: IrBlock) -> ScheduleBlock: - """Convert PulseIR to ScheduleBlock. - - Args: - pulse_ir: PulseIR to convert. - - Returns: - ScheduleBlock that end-user may interact with. - """ - out = ScheduleBlock(alignment_context=pulse_ir.alignment) - - def _unwrap_recursive(_elm): - if isinstance(_elm, IrBlock): - return ir_to_schedule(_elm) - return _elm.instruction - - for element in pulse_ir.elements: - unwrapped_element = _unwrap_recursive(element) - out.append(unwrapped_element, inplace=True) - - return out diff --git a/qiskit/pulse/compiler/passmanager.py b/qiskit/pulse/compiler/passmanager.py index 1eab9b0cb525..b86f36963dd9 100644 --- a/qiskit/pulse/compiler/passmanager.py +++ b/qiskit/pulse/compiler/passmanager.py @@ -10,63 +10,73 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Pass manager for pulse schedules.""" +"""Pass manager for pulse programs.""" 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 IrBlock -from qiskit.pulse.compiler.converters import schedule_to_ir, ir_to_schedule +from qiskit.pulse.ir import IrBlock, IrInstruction from qiskit.pulse.schedule import ScheduleBlock -class PulsePassManager(BasePassManager): - """A pass manager for compiling Qiskit ScheduleBlock programs.""" +PulseProgramT = Any +"""Type alias representing whatever pulse programs.""" + + +class BasePulsePassManager(BasePassManager, ABC): + """A pulse compiler base class. + + The pulse pass manager takes :class:`.ScheduleBlock` as an input and schedules + instructions within the block context according to the alignment specification + as a part of lowering. + + Since pulse sequence is a lower-end representation of quantum programs, + the compiler may require detailed description of the + target control electronics to generate functional output programs. + Qiskit :class:`~qiskit.provider.Backend` object may inject vendor specific + plugin passes that may consume such hardware specification + that the vendor may also provide as a custom :class:`.Target` model. + + Qiskit pulse pass manager relies on the :class:`.IrBlock` as an intermediate + representation on which all compiler passes are applied. + A developer must define a subclass of the ``BasePulsePassManager`` for + each desired compiler backend representation along with the logic to + interface with our intermediate representation. + """ def _passmanager_frontend( self, input_program: ScheduleBlock, **kwargs, ) -> IrBlock: - return schedule_to_ir(input_program) - - def _passmanager_backend( - self, - passmanager_ir: IrBlock, - in_program: ScheduleBlock, - **kwargs, - ) -> Any: - output = kwargs.get("output", "schedule_block") - if output == "pulse_ir": - return passmanager_ir - if output == "schedule_block": - return ir_to_schedule(passmanager_ir) + def _wrap_recursive(_prog): + _ret = IrBlock(alignment=input_program.alignment_context) + for _elm in _prog.blocks: + if isinstance(_elm, ScheduleBlock): + return _wrap_recursive(_elm) + _ret.add_element(IrInstruction(instruction=_elm)) + return _ret - raise ValueError(f"Specified target format '{output}' is not supported.") + return _wrap_recursive(input_program) # pylint: disable=arguments-differ def run( self, - schedules: ScheduleBlock | list[ScheduleBlock], - output: str = "schedule_block", + pulse_programs: ScheduleBlock | list[ScheduleBlock], callback: Callable | None = None, num_processes: int | None = None, - ) -> Any: - """Run all the passes on the input pulse schedules. + ) -> PulseProgramT | list[PulseProgramT]: + """Run all the passes on the input pulse programs. Args: - schedules: Input pulse programs to transform via all the registered passes. + pulse_programs: Input pulse programs to transform via all the registered passes. When a list of schedules are passed, the transform is performed in parallel for each input schedule with multiprocessing. - output: Format of the output program:: - - schedule_block: Return in :class:`.ScheduleBlock` format. - pulse_ir: Return in :class:`.PulseIR` format. - callback: A callback function that will be called after each pass execution. The function will be called with 5 keyword arguments:: @@ -98,11 +108,54 @@ def callback_func(**kwargs): to ``None`` the system default or local user configuration will be used. Returns: - The transformed program(s) in specified program format. + The transformed program(s). """ return super().run( - in_programs=schedules, + in_programs=pulse_programs, callback=callback, num_processes=num_processes, - output=output, ) + + +class BlockToIrCompiler(BasePulsePassManager): + """A specialized pulse compiler for IR backend. + + This compiler outputs :class:`.IrBlock`, which is an intermediate representation + of the pulse program in Qiskit. + """ + + def _passmanager_backend( + self, + passmanager_ir: IrBlock, + in_program: ScheduleBlock, + **kwargs, + ) -> IrBlock: + return passmanager_ir + + +class BlockTranspiler(BasePulsePassManager): + """A specialized pulse compiler for ScheduleBlock backend. + + This compiler (transpiler) outputs :class:`.ScheduleBlock`, which + is an identical data format to the input program. + """ + + def _passmanager_backend( + self, + passmanager_ir: IrBlock, + in_program: ScheduleBlock, + **kwargs, + ) -> ScheduleBlock: + + def _unwrap_recursive(_prog): + _ret = ScheduleBlock(alignment_context=passmanager_ir.alignment) + for _elm in _prog.elements: + if isinstance(_elm, IrBlock): + return _unwrap_recursive(_elm) + _ret.append(_elm.instruction, inplace=True) + return _ret + + out_block = _unwrap_recursive(passmanager_ir) + out_block.metadata = in_program.metadata.copy() + + return out_block diff --git a/test/python/pulse/_dummy_programs.py b/test/python/pulse/_dummy_programs.py index 1c26bf44f5e4..a60e2729bc9c 100644 --- a/test/python/pulse/_dummy_programs.py +++ b/test/python/pulse/_dummy_programs.py @@ -32,3 +32,34 @@ def play_gaussian() -> ScheduleBlock: channel=channels.DriveChannel(0), ) return out + + +def play_and_inner_block_sequential() -> ScheduleBlock: + """Create sequential schedule with nested program.""" + + with builder.build() as subroutine: + builder.shift_phase(1.0, channel=channels.DriveChannel(0)) + builder.play( + pulse=symbolic_pulses.Gaussian( + duration=100, + amp=0.1, + sigma=25, + angle=0.0, + ), + channel=channels.DriveChannel(0), + ) + + with builder.build() as out: + with builder.align_sequential(): + builder.append_schedule(subroutine) + builder.play( + pulse=symbolic_pulses.Gaussian( + duration=40, + amp=0.1, + sigma=10, + angle=0.0, + ), + channel=channels.DriveChannel(1), + ) + + return out diff --git a/test/python/pulse/test_compile.py b/test/python/pulse/test_compile.py index f76809fff342..697cbd3b5a5b 100644 --- a/test/python/pulse/test_compile.py +++ b/test/python/pulse/test_compile.py @@ -12,7 +12,7 @@ """A test cases for pulse compilation.""" -from qiskit.pulse.compiler import PulsePassManager +from qiskit.pulse.compiler import BlockTranspiler from test import QiskitTestCase # pylint: disable=wrong-import-order from . import _dummy_programs as schedule_lib @@ -21,11 +21,20 @@ class TestCompiler(QiskitTestCase): """Test case for pulse compiler.""" - def test_roundtrip(self): + def test_roundtrip_simple(self): """Test just returns the input program.""" # Just convert an input to PulseIR and convert it back to ScheduleBlock. - pm = PulsePassManager() + pm = BlockTranspiler() in_prog = schedule_lib.play_gaussian() - out_prog = pm.run(schedules=in_prog, output="schedule_block") + out_prog = pm.run(pulse_programs=in_prog) + self.assertEqual(in_prog, out_prog) + + def test_roundtrip_nested(self): + """Test just returns the input program.""" + # Just convert an input to PulseIR and convert it back to ScheduleBlock. + pm = BlockTranspiler() + + in_prog = schedule_lib.play_and_inner_block_sequential() + out_prog = pm.run(pulse_programs=in_prog) self.assertEqual(in_prog, out_prog)