From ca8a36ae699de5e5ec84a3d80b42196f9908943d Mon Sep 17 00:00:00 2001 From: Mehrdad Malekmohammadi Date: Wed, 28 May 2025 15:17:15 -0400 Subject: [PATCH 1/8] Convert keep_intermediate to enum --- frontend/catalyst/compiler.py | 7 ++- frontend/catalyst/jit.py | 13 +++-- frontend/catalyst/pipelines.py | 45 +++++++++++++++-- frontend/test/pytest/test_compiler.py | 70 ++++++++++++++++++++++++++- 4 files changed, 125 insertions(+), 10 deletions(-) diff --git a/frontend/catalyst/compiler.py b/frontend/catalyst/compiler.py index 5dcc98a5c8..19e8c766ac 100644 --- a/frontend/catalyst/compiler.py +++ b/frontend/catalyst/compiler.py @@ -29,7 +29,7 @@ from typing import List, Optional from catalyst.logging import debug_logger, debug_logger_init -from catalyst.pipelines import CompileOptions +from catalyst.pipelines import CompileOptions, KeepIntermediateLevel from catalyst.utils.exceptions import CompileError from catalyst.utils.filesystem import Directory from catalyst.utils.runtime_environment import get_cli_path, get_lib_path @@ -342,7 +342,7 @@ def _options_to_cli_flags(options): if not options.lower_to_llvm: extra_args += [("--tool", "opt")] - if options.keep_intermediate: + if options.keep_intermediate >= KeepIntermediateLevel.BASIC: extra_args += ["--keep-intermediate"] if options.verbose: @@ -351,6 +351,9 @@ def _options_to_cli_flags(options): if options.async_qnodes: # pragma: nocover extra_args += ["--async-qnodes"] + if options.keep_intermediate >= KeepIntermediateLevel.DEBUG: + extra_args += ["--save-ir-after-each=pass"] + return extra_args diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 7c260ceacb..e12a6a3c17 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -118,10 +118,15 @@ def qjit( async_qnodes (bool): Experimental support for automatically executing QNodes asynchronously, if supported by the device runtime. target (str): the compilation target - keep_intermediate (bool): Whether or not to store the intermediate files throughout the - compilation. If ``True``, intermediate representations are available via the - :attr:`~.QJIT.mlir`, :attr:`~.QJIT.mlir_opt`, :attr:`~.QJIT.jaxpr`, - and :attr:`~.QJIT.qir`, representing different stages in the optimization process. + keep_intermediate (Union[str, int, bool]): Level controlling intermediate file generation. + - ``False`` or ``0`` or ``"none"`` (default): No intermediate files are kept. + - ``True`` or ``1`` or ``"basic"``: Standard intermediate files are kept. + - ``2`` or ``"debug"``: Standard intermediate files are kept, and IR is saved after each pass. + If enabled, intermediate representations are also available via the following attributes: + - :attr:`~.QJIT.jaxpr`: JAX program representation + - :attr:`~.QJIT.mlir`: MLIR representation after canonicalization + - :attr:`~.QJIT.mlir_opt`: MLIR representation after optimization + - :attr:`~.QJIT.qir`: QIR in LLVM IR form verbose (bool): If ``True``, the tools and flags used by Catalyst behind the scenes are printed out. logfile (Optional[TextIOWrapper]): File object to write verbose messages to (default - diff --git a/frontend/catalyst/pipelines.py b/frontend/catalyst/pipelines.py index 67a6e4ef45..cf93b24d08 100644 --- a/frontend/catalyst/pipelines.py +++ b/frontend/catalyst/pipelines.py @@ -25,6 +25,7 @@ """ +import enum import sys from copy import deepcopy from dataclasses import dataclass @@ -37,6 +38,14 @@ from catalyst.utils.exceptions import CompileError +class KeepIntermediateLevel(enum.IntEnum): + """Enum to control the level of intermediate file keeping.""" + + NONE = 0 # No intermediate files are kept. + BASIC = 1 # Standard intermediate files are kept. + DEBUG = 2 # Standard intermediate files are kept, and IR is saved after each pass. + + # pylint: disable=too-many-instance-attributes @dataclass class CompileOptions: @@ -47,8 +56,10 @@ class CompileOptions: Default is ``False`` logfile (Optional[TextIOWrapper]): the logfile to write output to. Default is ``sys.stderr`` - keep_intermediate (Optional[bool]): flag indicating whether to keep intermediate results. - Default is ``False`` + keep_intermediate (Optional[Union[str, int, bool]]): Level controlling intermediate file generation. + - ``False`` or ``0`` or ``"none"`` (default): No intermediate files are kept. + - ``True`` or ``1`` or ``"basic"``: Standard intermediate files are kept. + - ``2`` or ``"debug"``: Standard intermediate files are kept, and IR is saved after each pass. pipelines (Optional[List[Tuple[str,List[str]]]]): A list of tuples. The first entry of the tuple corresponds to the name of a pipeline. The second entry of the tuple corresponds to a list of MLIR passes. @@ -79,7 +90,7 @@ class CompileOptions: verbose: Optional[bool] = False logfile: Optional[TextIOWrapper] = sys.stderr target: Optional[str] = "binary" - keep_intermediate: Optional[bool] = False + keep_intermediate: Optional[Union[str, int, bool, KeepIntermediateLevel]] = False pipelines: Optional[List[Any]] = None autograph: Optional[bool] = False autograph_include: Optional[Iterable[str]] = () @@ -96,6 +107,34 @@ class CompileOptions: dialect_plugins: Optional[Set[Path]] = None def __post_init__(self): + # Convert keep_intermediate to Enum + if self.keep_intermediate is None: + self.keep_intermediate = KeepIntermediateLevel.NONE + elif isinstance(self.keep_intermediate, bool): + self.keep_intermediate = ( + KeepIntermediateLevel.BASIC + if self.keep_intermediate + else KeepIntermediateLevel.NONE + ) + elif isinstance(self.keep_intermediate, int): + try: + self.keep_intermediate = KeepIntermediateLevel(self.keep_intermediate) + except ValueError as e: + raise ValueError( + f"Invalid int for keep_intermediate: {self.keep_intermediate}. " + "Valid integers are 0, 1, 2." + ) from e + elif isinstance(self.keep_intermediate, str): + try: + self.keep_intermediate = KeepIntermediateLevel[self.keep_intermediate.upper()] + except KeyError as e: + raise ValueError( + f"Invalid string for keep_intermediate: {self.keep_intermediate}. " + "Valid strings are 'none', 'basic', 'debug'." + ) from e + elif not isinstance(self.keep_intermediate, KeepIntermediateLevel): + raise TypeError(f"Invalid type for keep_intermediate: {type(self.keep_intermediate)}.") + # Check that async runs must not be seeded if self.async_qnodes and self.seed is not None: raise CompileError( diff --git a/frontend/test/pytest/test_compiler.py b/frontend/test/pytest/test_compiler.py index 63f684a960..2b348cbab5 100644 --- a/frontend/test/pytest/test_compiler.py +++ b/frontend/test/pytest/test_compiler.py @@ -30,8 +30,9 @@ import pytest from catalyst import qjit -from catalyst.compiler import CompileOptions, Compiler, LinkerDriver +from catalyst.compiler import CompileOptions, Compiler, LinkerDriver, _options_to_cli_flags from catalyst.debug import instrumentation +from catalyst.pipelines import KeepIntermediateLevel from catalyst.utils.exceptions import CompileError from catalyst.utils.filesystem import Directory @@ -92,6 +93,73 @@ def circuit(): capture = capture_result.out + capture_result.err assert "[DIAGNOSTICS]" in capture + @pytest.mark.parametrize( + "input_value, expected_level", + [ + (None, KeepIntermediateLevel.NONE), + (False, KeepIntermediateLevel.NONE), + (True, KeepIntermediateLevel.BASIC), + (0, KeepIntermediateLevel.NONE), + (1, KeepIntermediateLevel.BASIC), + (2, KeepIntermediateLevel.DEBUG), + ("none", KeepIntermediateLevel.NONE), + ("NONE", KeepIntermediateLevel.NONE), + ("basic", KeepIntermediateLevel.BASIC), + ("BASIC", KeepIntermediateLevel.BASIC), + ("debug", KeepIntermediateLevel.DEBUG), + ("DEBUG", KeepIntermediateLevel.DEBUG), + (KeepIntermediateLevel.NONE, KeepIntermediateLevel.NONE), + (KeepIntermediateLevel.BASIC, KeepIntermediateLevel.BASIC), + (KeepIntermediateLevel.DEBUG, KeepIntermediateLevel.DEBUG), + ], + ) + def test_keep_intermediate_levels_conversion(self, input_value, expected_level): + """Test that various inputs for keep_intermediate are correctly converted to Enum.""" + options = CompileOptions(keep_intermediate=input_value) + assert options.keep_intermediate == expected_level + + @pytest.mark.parametrize( + "invalid_input, error_type, error_match", + [ + (3, ValueError, "Invalid int for keep_intermediate: 3. Valid integers are 0, 1, 2."), + (-1, ValueError, "Invalid int for keep_intermediate: -1. Valid integers are 0, 1, 2."), + ( + "invalid_string", + ValueError, + "Invalid string for keep_intermediate: invalid_string. Valid strings are 'none', 'basic', 'debug'.", + ), + (3.0, TypeError, "Invalid type for keep_intermediate: ."), + ([], TypeError, "Invalid type for keep_intermediate: ."), + ], + ) + def test_keep_intermediate_invalid_inputs(self, invalid_input, error_type, error_match): + """Test that invalid inputs for keep_intermediate raise appropriate errors.""" + with pytest.raises(error_type, match=error_match): + CompileOptions(keep_intermediate=invalid_input) + + def test_options_to_cli_flags_keep_intermediate_none(self): + """Test _options_to_cli_flags with KeepIntermediateLevel.NONE.""" + options = CompileOptions(keep_intermediate=KeepIntermediateLevel.NONE) + flags = _options_to_cli_flags(options) + assert "--keep-intermediate" not in flags + assert "--save-ir-after-each=pass" not in flags + + def test_options_to_cli_flags_keep_intermediate_basic(self): + """Test _options_to_cli_flags with KeepIntermediateLevel.BASIC.""" + options = CompileOptions(keep_intermediate=KeepIntermediateLevel.BASIC) + flags = _options_to_cli_flags(options) + assert "--keep-intermediate" in flags + assert "--save-ir-after-each=pass" not in flags + assert flags.count("--save-ir-after-each=pass") <= 1 + + def test_options_to_cli_flags_keep_intermediate_debug(self): + """Test _options_to_cli_flags with KeepIntermediateLevel.DEBUG.""" + options = CompileOptions(keep_intermediate=KeepIntermediateLevel.DEBUG) + flags = _options_to_cli_flags(options) + assert "--keep-intermediate" in flags + assert "--save-ir-after-each=pass" in flags + assert flags.count("--save-ir-after-each=pass") == 1 + class TestCompilerWarnings: """Test compiler's warning messages.""" From 9344ac14600449d6f456ba0b87898135527fa4d8 Mon Sep 17 00:00:00 2001 From: Mehrdad Malekmohammadi Date: Fri, 6 Jun 2025 09:54:59 -0400 Subject: [PATCH 2/8] format --- frontend/catalyst/jit.py | 5 +++-- frontend/catalyst/pipelines.py | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index e12a6a3c17..9b1b2a5234 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -121,8 +121,9 @@ def qjit( keep_intermediate (Union[str, int, bool]): Level controlling intermediate file generation. - ``False`` or ``0`` or ``"none"`` (default): No intermediate files are kept. - ``True`` or ``1`` or ``"basic"``: Standard intermediate files are kept. - - ``2`` or ``"debug"``: Standard intermediate files are kept, and IR is saved after each pass. - If enabled, intermediate representations are also available via the following attributes: + - ``2`` or ``"debug"``: Standard intermediate files are kept, and IR is saved after + each pass. + If enabled, intermediate representations are available via the following attributes: - :attr:`~.QJIT.jaxpr`: JAX program representation - :attr:`~.QJIT.mlir`: MLIR representation after canonicalization - :attr:`~.QJIT.mlir_opt`: MLIR representation after optimization diff --git a/frontend/catalyst/pipelines.py b/frontend/catalyst/pipelines.py index cf93b24d08..afc6139d9d 100644 --- a/frontend/catalyst/pipelines.py +++ b/frontend/catalyst/pipelines.py @@ -56,10 +56,12 @@ class CompileOptions: Default is ``False`` logfile (Optional[TextIOWrapper]): the logfile to write output to. Default is ``sys.stderr`` - keep_intermediate (Optional[Union[str, int, bool]]): Level controlling intermediate file generation. + keep_intermediate (Optional[Union[str, int, bool]]): Level controlling intermediate file + generation. - ``False`` or ``0`` or ``"none"`` (default): No intermediate files are kept. - ``True`` or ``1`` or ``"basic"``: Standard intermediate files are kept. - - ``2`` or ``"debug"``: Standard intermediate files are kept, and IR is saved after each pass. + - ``2`` or ``"debug"``: Standard intermediate files are kept, and IR is saved after + each pass. pipelines (Optional[List[Tuple[str,List[str]]]]): A list of tuples. The first entry of the tuple corresponds to the name of a pipeline. The second entry of the tuple corresponds to a list of MLIR passes. From 386910150e4ebc0e64b6dbd9a3e6c505f3b4908e Mon Sep 17 00:00:00 2001 From: Mehrdad Malekmohammadi Date: Fri, 6 Jun 2025 09:56:54 -0400 Subject: [PATCH 3/8] format --- frontend/test/pytest/test_compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/test/pytest/test_compiler.py b/frontend/test/pytest/test_compiler.py index 2b348cbab5..4b10a3a2d7 100644 --- a/frontend/test/pytest/test_compiler.py +++ b/frontend/test/pytest/test_compiler.py @@ -126,7 +126,7 @@ def test_keep_intermediate_levels_conversion(self, input_value, expected_level): ( "invalid_string", ValueError, - "Invalid string for keep_intermediate: invalid_string. Valid strings are 'none', 'basic', 'debug'.", + "Invalid string for keep_intermediate: Valid strings are 'none', 'basic', 'debug'.", ), (3.0, TypeError, "Invalid type for keep_intermediate: ."), ([], TypeError, "Invalid type for keep_intermediate: ."), From 77bd5e9e2931dc6775df3383dd8c2447556d8248 Mon Sep 17 00:00:00 2001 From: Mehrdad Malekmohammadi Date: Fri, 6 Jun 2025 11:38:34 -0400 Subject: [PATCH 4/8] format and change the level names --- frontend/catalyst/compiler.py | 4 +- frontend/catalyst/jit.py | 5 +-- frontend/catalyst/pipelines.py | 63 ++++++++++++++------------- frontend/test/pytest/test_compiler.py | 29 ++++++------ 4 files changed, 51 insertions(+), 50 deletions(-) diff --git a/frontend/catalyst/compiler.py b/frontend/catalyst/compiler.py index 19e8c766ac..a303392bf6 100644 --- a/frontend/catalyst/compiler.py +++ b/frontend/catalyst/compiler.py @@ -342,7 +342,7 @@ def _options_to_cli_flags(options): if not options.lower_to_llvm: extra_args += [("--tool", "opt")] - if options.keep_intermediate >= KeepIntermediateLevel.BASIC: + if options.keep_intermediate >= KeepIntermediateLevel.PIPELINE: extra_args += ["--keep-intermediate"] if options.verbose: @@ -351,7 +351,7 @@ def _options_to_cli_flags(options): if options.async_qnodes: # pragma: nocover extra_args += ["--async-qnodes"] - if options.keep_intermediate >= KeepIntermediateLevel.DEBUG: + if options.keep_intermediate >= KeepIntermediateLevel.PASS: extra_args += ["--save-ir-after-each=pass"] return extra_args diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 9b1b2a5234..686459c56d 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -120,9 +120,8 @@ def qjit( target (str): the compilation target keep_intermediate (Union[str, int, bool]): Level controlling intermediate file generation. - ``False`` or ``0`` or ``"none"`` (default): No intermediate files are kept. - - ``True`` or ``1`` or ``"basic"``: Standard intermediate files are kept. - - ``2`` or ``"debug"``: Standard intermediate files are kept, and IR is saved after - each pass. + - ``True`` or ``1`` or ``"pipeline"``: Intermediate files are saved after each pipeline. + - ``2`` or ``"pass"``: Intermediate files are saved after each pass. If enabled, intermediate representations are available via the following attributes: - :attr:`~.QJIT.jaxpr`: JAX program representation - :attr:`~.QJIT.mlir`: MLIR representation after canonicalization diff --git a/frontend/catalyst/pipelines.py b/frontend/catalyst/pipelines.py index afc6139d9d..d9554e172e 100644 --- a/frontend/catalyst/pipelines.py +++ b/frontend/catalyst/pipelines.py @@ -42,8 +42,35 @@ class KeepIntermediateLevel(enum.IntEnum): """Enum to control the level of intermediate file keeping.""" NONE = 0 # No intermediate files are kept. - BASIC = 1 # Standard intermediate files are kept. - DEBUG = 2 # Standard intermediate files are kept, and IR is saved after each pass. + PIPELINE = 1 # Intermediate files are saved after each pipeline. + PASS = 2 # Intermediate files are saved after each pass. + + +def _parse_keep_intermediate( + level: Union[str, int, bool, KeepIntermediateLevel, None] +) -> KeepIntermediateLevel: + """Parse the keep_intermediate value into a KeepIntermediateLevel enum.""" + if level is None: + return KeepIntermediateLevel.NONE + elif isinstance(level, bool): + return KeepIntermediateLevel.PIPELINE if level else KeepIntermediateLevel.NONE + elif isinstance(level, int): + try: + return KeepIntermediateLevel(level) + except ValueError as e: + raise ValueError( + f"Invalid int for keep_intermediate: {level}. " "Valid integers are 0, 1, 2." + ) from e + elif isinstance(level, str): + try: + return KeepIntermediateLevel[level.upper()] + except KeyError as e: + raise ValueError( + f"Invalid string for keep_intermediate: {level}. " + "Valid strings are 'none', 'pipeline', 'pass'." + ) from e + elif not isinstance(level, KeepIntermediateLevel): + raise TypeError(f"Invalid type for keep_intermediate: {type(level)}.") # pylint: disable=too-many-instance-attributes @@ -59,9 +86,8 @@ class CompileOptions: keep_intermediate (Optional[Union[str, int, bool]]): Level controlling intermediate file generation. - ``False`` or ``0`` or ``"none"`` (default): No intermediate files are kept. - - ``True`` or ``1`` or ``"basic"``: Standard intermediate files are kept. - - ``2`` or ``"debug"``: Standard intermediate files are kept, and IR is saved after - each pass. + - ``True`` or ``1`` or ``"pipeline"``: Intermediate files are saved after each pipeline. + - ``2`` or ``"pass"``: Intermediate files are saved after each pass. pipelines (Optional[List[Tuple[str,List[str]]]]): A list of tuples. The first entry of the tuple corresponds to the name of a pipeline. The second entry of the tuple corresponds to a list of MLIR passes. @@ -110,32 +136,7 @@ class CompileOptions: def __post_init__(self): # Convert keep_intermediate to Enum - if self.keep_intermediate is None: - self.keep_intermediate = KeepIntermediateLevel.NONE - elif isinstance(self.keep_intermediate, bool): - self.keep_intermediate = ( - KeepIntermediateLevel.BASIC - if self.keep_intermediate - else KeepIntermediateLevel.NONE - ) - elif isinstance(self.keep_intermediate, int): - try: - self.keep_intermediate = KeepIntermediateLevel(self.keep_intermediate) - except ValueError as e: - raise ValueError( - f"Invalid int for keep_intermediate: {self.keep_intermediate}. " - "Valid integers are 0, 1, 2." - ) from e - elif isinstance(self.keep_intermediate, str): - try: - self.keep_intermediate = KeepIntermediateLevel[self.keep_intermediate.upper()] - except KeyError as e: - raise ValueError( - f"Invalid string for keep_intermediate: {self.keep_intermediate}. " - "Valid strings are 'none', 'basic', 'debug'." - ) from e - elif not isinstance(self.keep_intermediate, KeepIntermediateLevel): - raise TypeError(f"Invalid type for keep_intermediate: {type(self.keep_intermediate)}.") + self.keep_intermediate = _parse_keep_intermediate(self.keep_intermediate) # Check that async runs must not be seeded if self.async_qnodes and self.seed is not None: diff --git a/frontend/test/pytest/test_compiler.py b/frontend/test/pytest/test_compiler.py index 4b10a3a2d7..45ce2e1bfb 100644 --- a/frontend/test/pytest/test_compiler.py +++ b/frontend/test/pytest/test_compiler.py @@ -98,19 +98,19 @@ def circuit(): [ (None, KeepIntermediateLevel.NONE), (False, KeepIntermediateLevel.NONE), - (True, KeepIntermediateLevel.BASIC), + (True, KeepIntermediateLevel.PIPELINE), (0, KeepIntermediateLevel.NONE), - (1, KeepIntermediateLevel.BASIC), - (2, KeepIntermediateLevel.DEBUG), + (1, KeepIntermediateLevel.PIPELINE), + (2, KeepIntermediateLevel.PASS), ("none", KeepIntermediateLevel.NONE), ("NONE", KeepIntermediateLevel.NONE), - ("basic", KeepIntermediateLevel.BASIC), - ("BASIC", KeepIntermediateLevel.BASIC), - ("debug", KeepIntermediateLevel.DEBUG), - ("DEBUG", KeepIntermediateLevel.DEBUG), + ("pipeline", KeepIntermediateLevel.PIPELINE), + ("PIPELINE", KeepIntermediateLevel.PIPELINE), + ("pass", KeepIntermediateLevel.PASS), + ("PASS", KeepIntermediateLevel.PASS), (KeepIntermediateLevel.NONE, KeepIntermediateLevel.NONE), - (KeepIntermediateLevel.BASIC, KeepIntermediateLevel.BASIC), - (KeepIntermediateLevel.DEBUG, KeepIntermediateLevel.DEBUG), + (KeepIntermediateLevel.PIPELINE, KeepIntermediateLevel.PIPELINE), + (KeepIntermediateLevel.PASS, KeepIntermediateLevel.PASS), ], ) def test_keep_intermediate_levels_conversion(self, input_value, expected_level): @@ -126,7 +126,8 @@ def test_keep_intermediate_levels_conversion(self, input_value, expected_level): ( "invalid_string", ValueError, - "Invalid string for keep_intermediate: Valid strings are 'none', 'basic', 'debug'.", + "Invalid string for keep_intermediate: invalid_string. Valid strings are 'none'," + " 'pipeline', 'pass'.", ), (3.0, TypeError, "Invalid type for keep_intermediate: ."), ([], TypeError, "Invalid type for keep_intermediate: ."), @@ -145,16 +146,16 @@ def test_options_to_cli_flags_keep_intermediate_none(self): assert "--save-ir-after-each=pass" not in flags def test_options_to_cli_flags_keep_intermediate_basic(self): - """Test _options_to_cli_flags with KeepIntermediateLevel.BASIC.""" - options = CompileOptions(keep_intermediate=KeepIntermediateLevel.BASIC) + """Test _options_to_cli_flags with KeepIntermediateLevel.PIPELINE.""" + options = CompileOptions(keep_intermediate=KeepIntermediateLevel.PIPELINE) flags = _options_to_cli_flags(options) assert "--keep-intermediate" in flags assert "--save-ir-after-each=pass" not in flags assert flags.count("--save-ir-after-each=pass") <= 1 def test_options_to_cli_flags_keep_intermediate_debug(self): - """Test _options_to_cli_flags with KeepIntermediateLevel.DEBUG.""" - options = CompileOptions(keep_intermediate=KeepIntermediateLevel.DEBUG) + """Test _options_to_cli_flags with KeepIntermediateLevel.PASS.""" + options = CompileOptions(keep_intermediate=KeepIntermediateLevel.PASS) flags = _options_to_cli_flags(options) assert "--keep-intermediate" in flags assert "--save-ir-after-each=pass" in flags From cb9192bf78b0c215f72da64a297643a8caf088d6 Mon Sep 17 00:00:00 2001 From: Mehrdad Malekmohammadi Date: Fri, 6 Jun 2025 11:47:46 -0400 Subject: [PATCH 5/8] format --- frontend/catalyst/pipelines.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/catalyst/pipelines.py b/frontend/catalyst/pipelines.py index d9554e172e..e8b65b6407 100644 --- a/frontend/catalyst/pipelines.py +++ b/frontend/catalyst/pipelines.py @@ -47,10 +47,12 @@ class KeepIntermediateLevel(enum.IntEnum): def _parse_keep_intermediate( - level: Union[str, int, bool, KeepIntermediateLevel, None] + level: Union[str, int, bool, KeepIntermediateLevel, None], ) -> KeepIntermediateLevel: """Parse the keep_intermediate value into a KeepIntermediateLevel enum.""" - if level is None: + if isinstance(level, KeepIntermediateLevel): + return level + elif level is None: return KeepIntermediateLevel.NONE elif isinstance(level, bool): return KeepIntermediateLevel.PIPELINE if level else KeepIntermediateLevel.NONE @@ -69,8 +71,7 @@ def _parse_keep_intermediate( f"Invalid string for keep_intermediate: {level}. " "Valid strings are 'none', 'pipeline', 'pass'." ) from e - elif not isinstance(level, KeepIntermediateLevel): - raise TypeError(f"Invalid type for keep_intermediate: {type(level)}.") + raise TypeError(f"Invalid type for keep_intermediate: {type(level)}.") # pylint: disable=too-many-instance-attributes @@ -83,7 +84,7 @@ class CompileOptions: Default is ``False`` logfile (Optional[TextIOWrapper]): the logfile to write output to. Default is ``sys.stderr`` - keep_intermediate (Optional[Union[str, int, bool]]): Level controlling intermediate file + keep_intermediate (Optional[Union[str, int, bool]]): Level controlling intermediate file generation. - ``False`` or ``0`` or ``"none"`` (default): No intermediate files are kept. - ``True`` or ``1`` or ``"pipeline"``: Intermediate files are saved after each pipeline. From 5efe481e498e50b7a88a8c101e64e92b468a6015 Mon Sep 17 00:00:00 2001 From: Mehrdad Malekmohammadi Date: Fri, 6 Jun 2025 13:12:53 -0400 Subject: [PATCH 6/8] add changelog --- doc/releases/changelog-dev.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 3b9ac81c4f..3a3e8ae1e8 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -95,6 +95,14 @@ performance by eliminating indirect conversion. [(#1738)](https://github.com/PennyLaneAI/catalyst/pull/1738) +* The `keep_intermediate` argument in the `qjit` decorator now accepts a new value that allows for + saving intermediate files after each pass. The updated possible options for this argument are: + * `False` or `0` or `"none"`: No intermediate files are kept. + * `True` or `1` or `"pipeline"`: Intermediate files are saved after each pipeline. + * `2` or `"pass"`: Intermediate files are saved after each pass. + The default value is `False`. + [(#1791)](https://github.com/PennyLaneAI/catalyst/pull/1791) +

Breaking changes 💔

* (Device Developers Only) The `QuantumDevice` interface in the Catalyst Runtime plugin system From 03c32d794d2a820985dfd1bfc83847b2cc43423e Mon Sep 17 00:00:00 2001 From: Mehrdad Malekmohammadi Date: Fri, 6 Jun 2025 14:19:19 -0400 Subject: [PATCH 7/8] simpler logic --- frontend/catalyst/pipelines.py | 34 ++++++++++----------------- frontend/test/pytest/test_compiler.py | 27 +++------------------ 2 files changed, 16 insertions(+), 45 deletions(-) diff --git a/frontend/catalyst/pipelines.py b/frontend/catalyst/pipelines.py index e8b65b6407..916d2b3634 100644 --- a/frontend/catalyst/pipelines.py +++ b/frontend/catalyst/pipelines.py @@ -47,31 +47,23 @@ class KeepIntermediateLevel(enum.IntEnum): def _parse_keep_intermediate( - level: Union[str, int, bool, KeepIntermediateLevel, None], + level: Union[str, int, bool], ) -> KeepIntermediateLevel: """Parse the keep_intermediate value into a KeepIntermediateLevel enum.""" - if isinstance(level, KeepIntermediateLevel): - return level - elif level is None: - return KeepIntermediateLevel.NONE - elif isinstance(level, bool): - return KeepIntermediateLevel.PIPELINE if level else KeepIntermediateLevel.NONE - elif isinstance(level, int): - try: + match level: + case 0 | 1 | 2: return KeepIntermediateLevel(level) - except ValueError as e: + case "none": + return KeepIntermediateLevel.NONE + case "pipeline": + return KeepIntermediateLevel.PIPELINE + case "pass": + return KeepIntermediateLevel.PASS + case _: raise ValueError( - f"Invalid int for keep_intermediate: {level}. " "Valid integers are 0, 1, 2." - ) from e - elif isinstance(level, str): - try: - return KeepIntermediateLevel[level.upper()] - except KeyError as e: - raise ValueError( - f"Invalid string for keep_intermediate: {level}. " - "Valid strings are 'none', 'pipeline', 'pass'." - ) from e - raise TypeError(f"Invalid type for keep_intermediate: {type(level)}.") + f"Invalid value for keep_intermediate: {level}. " + "Valid values are True, False, 0, 1, 2, 'none', 'pipeline', 'pass'." + ) # pylint: disable=too-many-instance-attributes diff --git a/frontend/test/pytest/test_compiler.py b/frontend/test/pytest/test_compiler.py index 45ce2e1bfb..6b9d721724 100644 --- a/frontend/test/pytest/test_compiler.py +++ b/frontend/test/pytest/test_compiler.py @@ -96,21 +96,14 @@ def circuit(): @pytest.mark.parametrize( "input_value, expected_level", [ - (None, KeepIntermediateLevel.NONE), (False, KeepIntermediateLevel.NONE), (True, KeepIntermediateLevel.PIPELINE), (0, KeepIntermediateLevel.NONE), (1, KeepIntermediateLevel.PIPELINE), (2, KeepIntermediateLevel.PASS), ("none", KeepIntermediateLevel.NONE), - ("NONE", KeepIntermediateLevel.NONE), ("pipeline", KeepIntermediateLevel.PIPELINE), - ("PIPELINE", KeepIntermediateLevel.PIPELINE), ("pass", KeepIntermediateLevel.PASS), - ("PASS", KeepIntermediateLevel.PASS), - (KeepIntermediateLevel.NONE, KeepIntermediateLevel.NONE), - (KeepIntermediateLevel.PIPELINE, KeepIntermediateLevel.PIPELINE), - (KeepIntermediateLevel.PASS, KeepIntermediateLevel.PASS), ], ) def test_keep_intermediate_levels_conversion(self, input_value, expected_level): @@ -118,24 +111,10 @@ def test_keep_intermediate_levels_conversion(self, input_value, expected_level): options = CompileOptions(keep_intermediate=input_value) assert options.keep_intermediate == expected_level - @pytest.mark.parametrize( - "invalid_input, error_type, error_match", - [ - (3, ValueError, "Invalid int for keep_intermediate: 3. Valid integers are 0, 1, 2."), - (-1, ValueError, "Invalid int for keep_intermediate: -1. Valid integers are 0, 1, 2."), - ( - "invalid_string", - ValueError, - "Invalid string for keep_intermediate: invalid_string. Valid strings are 'none'," - " 'pipeline', 'pass'.", - ), - (3.0, TypeError, "Invalid type for keep_intermediate: ."), - ([], TypeError, "Invalid type for keep_intermediate: ."), - ], - ) - def test_keep_intermediate_invalid_inputs(self, invalid_input, error_type, error_match): + @pytest.mark.parametrize("invalid_input", [3, -1, "invalid_string", 3.0, []]) + def test_keep_intermediate_invalid_inputs(self, invalid_input): """Test that invalid inputs for keep_intermediate raise appropriate errors.""" - with pytest.raises(error_type, match=error_match): + with pytest.raises(ValueError, match=f"Invalid value for keep_intermediate: "): CompileOptions(keep_intermediate=invalid_input) def test_options_to_cli_flags_keep_intermediate_none(self): From 2b5a5afc771d749246c09f40dad8f90f5e1f8c7f Mon Sep 17 00:00:00 2001 From: Mehrdad Malekmohammadi Date: Fri, 6 Jun 2025 14:21:07 -0400 Subject: [PATCH 8/8] format --- frontend/test/pytest/test_compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/test/pytest/test_compiler.py b/frontend/test/pytest/test_compiler.py index 6b9d721724..5f7c4cd226 100644 --- a/frontend/test/pytest/test_compiler.py +++ b/frontend/test/pytest/test_compiler.py @@ -114,7 +114,7 @@ def test_keep_intermediate_levels_conversion(self, input_value, expected_level): @pytest.mark.parametrize("invalid_input", [3, -1, "invalid_string", 3.0, []]) def test_keep_intermediate_invalid_inputs(self, invalid_input): """Test that invalid inputs for keep_intermediate raise appropriate errors.""" - with pytest.raises(ValueError, match=f"Invalid value for keep_intermediate: "): + with pytest.raises(ValueError, match="Invalid value for keep_intermediate:"): CompileOptions(keep_intermediate=invalid_input) def test_options_to_cli_flags_keep_intermediate_none(self):