Source code for qurry.process.utils.bit_slice

"""Post Processing - Utils - Bit Slice (:mod:`qurry.process.utils.bit_slice`)"""

import warnings
from typing import Union, Optional, Sequence, TypeVar, overload

from ..availability import availablility
from ..exceptions import PostProcessingRustImportError, PostProcessingRustUnavailableWarning

try:
    from ...boorust import bit_slice  # type: ignore

    qubit_selector_rust_source = bit_slice.qubit_selector_rust  # type: ignore
    cycling_slice_rust_source = bit_slice.cycling_slice_rust  # type: ignore
    degree_handler_rust_source = bit_slice.degree_handler_rust

    RUST_AVAILABLE = True
    FAILED_RUST_IMPORT = None
except ImportError as err:
    RUST_AVAILABLE = False
    FAILED_RUST_IMPORT = err

    def qubit_selector_rust_source(*args, **kwargs):
        """Dummy function for cycling_slice_rust."""
        raise PostProcessingRustImportError(
            "Rust is not available, using python to calculate cycling slice."
            + f" More infomation about this error: {FAILED_RUST_IMPORT}",
        )

    def cycling_slice_rust_source(*args, **kwargs):
        """Dummy function for cycling_slice_rust."""
        raise PostProcessingRustImportError(
            "Rust is not available, using python to calculate cycling slice."
        ) from FAILED_RUST_IMPORT

    def degree_handler_rust_source(*args, **kwargs):
        """Dummy function for degree_handler_rust."""
        raise PostProcessingRustImportError(
            "Rust is not available, using python to calculate degree handler."
        ) from FAILED_RUST_IMPORT


BACKEND_AVAILABLE = availablility(
    "utils.bit_slice",
    [
        ("Rust", RUST_AVAILABLE, FAILED_RUST_IMPORT),
    ],
)


[docs] def qubit_selector( num_qubits: int, degree: Union[int, tuple[int, int], None] = None, ) -> tuple[int, int]: """Determint the qubits to be used. Args: num_qubits (int): Number of qubits. degree (Union[int, tuple[int, int], None], optional): Degree of freedom or specific subsystem range. Defaults to None then will use number of qubits as degree. Raises: ValueError: The specific degree of subsystem qubits beyond number of qubits which the wave function has. ValueError: The number of qubits of subsystem A is not a natural number. ValueError: Invalid input for subsystem range defined by only two integers. ValueError: Degree of freedom is not given. Returns: tuple[int]: The range of qubits to be used. """ subsystem = list(range(num_qubits)) if degree is None: degree = num_qubits if isinstance(degree, int): if degree > num_qubits: raise ValueError( f"The subsystem A includes {degree} qubits " + f"beyond {num_qubits} which the wave function has." ) if degree < 0: raise ValueError("The number of qubits of subsystem A has to be natural number.") item_range = (num_qubits - degree, num_qubits) subsystem = subsystem[num_qubits - degree : num_qubits] elif isinstance(degree, (tuple, list)): if len(degree) == 2: deg_parsed = ( degree if degree[0] < 0 < degree[1] else [(d % num_qubits if d != num_qubits else num_qubits) for d in degree] ) item_range = (min(deg_parsed), max(deg_parsed)) subsystem = subsystem[min(deg_parsed) : max(deg_parsed)] else: raise ValueError( "Subsystem range is defined by only two integers, " + f"but there is {len(degree)} integers in '{degree}'." ) else: raise ValueError(f"'degree' must be 'int' or 'tuple[int, int]', but get '{degree}'.") return item_range
[docs] def qubit_selector_rust( num_qubits: int, degree: Union[int, tuple[int, int], None] = None, ) -> tuple[int, int]: """Determint the qubits to be used. Args: num_qubits (int): Number of qubits. degree (Union[int, tuple[int, int], None], optional): Degree of freedom or specific subsystem range. Defaults to None then will use number of qubits as degree. Raises: ValueError: The specific degree of subsystem qubits beyond number of qubits which the wave function has. ValueError: The number of qubits of subsystem A is not a natural number. ValueError: Invalid input for subsystem range defined by only two integers. ValueError: Degree of freedom is not given. Returns: tuple[int]: The range of qubits to be used. """ if RUST_AVAILABLE: return qubit_selector_rust_source(num_qubits, degree) warnings.warn( "Rust is not available, using python to calculate qubit selector." + f" More infomation about this error: {FAILED_RUST_IMPORT}", category=PostProcessingRustUnavailableWarning, ) return qubit_selector(num_qubits, degree)
_ItemT = TypeVar("_ItemT") @overload def cycling_slice(target: list[_ItemT], start: int, end: int, step: int = 1) -> list[_ItemT]: ... @overload def cycling_slice(target: tuple[_ItemT], start: int, end: int, step: int = 1) -> tuple[_ItemT]: ... @overload def cycling_slice(target: str, start: int, end: int, step: int = 1) -> str: ...
[docs] def cycling_slice(target, start, end, step=1): """Slice a iterable object with cycling. Args: target (_SliceableT): The target object. start (int): Index of start. end (int): Index of end. step (int, optional): Step of slice. Defaults to 1. Raises: IndexError: Slice out of range. Returns: Iterable: The sliced object. """ length = len(target) slice_check = { f"start: {start} < -length: {-length}": (start < -length), f"length: {length} < end: {end}": (length < end), } if any(slice_check.values()): raise IndexError( "Slice out of range: " + ", ".join([f"{k}" for k, v in slice_check.items() if v]) + "." ) if length <= 0: return target if start < 0 <= end: new_string = target[start:] + target[:end] else: new_string = target[start:end] return new_string[::step]
[docs] def cycling_slice_rust(target: str, start: int, end: int, step: int = 1) -> str: """Slice a iterable object with cycling. Args: target (str): The target object. start (int): Index of start. end (int): Index of end. step (int, optional): Step of slice. Defaults to 1. Raises: IndexError: Slice out of range. Returns: str: The sliced object. """ if not isinstance(target, str): raise TypeError( f"Expect 'str' but get '{type(target)}'. cycling_slice_rust only support 'str'." ) if RUST_AVAILABLE: return cycling_slice_rust_source(target, start, end, step) warnings.warn( "Rust is not available, using python to calculate cycling slice." + f" Check: {FAILED_RUST_IMPORT}", PostProcessingRustUnavailableWarning, ) return cycling_slice(target, start, end, step)
[docs] def qubit_mapper_2_int( actual_num_qubits: int, selected_qubits: tuple[int, int], ) -> dict[int, int]: """Map the index of selected qubits to the index of the classical register. The selected qubits are defined by two integers. Args: actual_num_qubits (int): The actual number of qubits. selected_qubits (tuple[int, int]): The range of the selected qubits. Raises: ValueError: The range of qubits should be defined by two integers. ValueError: The selected qubits are beyond the number of qubits. ValueError: The selected qubits are not natural number. ValueError: The first integer should be less than the second integer. Returns: dict[int, int]: The mapping of the index of selected qubits to the index of the classical register. """ if not all(isinstance(qi, int) for qi in selected_qubits): raise ValueError( "The range of qubits should be defined by two integers, " + f"but get '{selected_qubits[0]}' with type '{type(selected_qubits[0])}' " + f"and '{selected_qubits[1]}' with type '{type(selected_qubits[1])}'." ) if selected_qubits[0] > actual_num_qubits or selected_qubits[1] > actual_num_qubits: raise ValueError( f"The selected qubits {selected_qubits} " + f"are beyond the number of qubits {actual_num_qubits}." ) if selected_qubits[0] >= selected_qubits[1]: raise ValueError( "The first integer should be less than the second integer " + f"when inputs as tuple, but get '{selected_qubits}'." ) qi_list = cycling_slice(list(range(actual_num_qubits)), selected_qubits[0], selected_qubits[1]) return {qi: ci for ci, qi in enumerate(qi_list)}
[docs] def qubit_mapper( actual_num_qubits: int, selected_qubits: Optional[Union[Sequence[int], int, tuple[int, int]]] = None, ) -> dict[int, int]: """Map the index of selected qubits to the index of the classical register. Args: actual_num_qubits (int): The actual number of qubits. selected_qubits (Optional[Union[Sequence[int], int, tuple[int, int]]], optional): The selected qubits. If it is None, then it will return the mapping of all qubits. If it is int, then it will return the mapping of the last n qubits. If it is tuple, then it will return the mapping of the qubits in the range. If it is list, then it will return the mapping of the selected qubits. Raises: ValueError: The range of qubits should be defined by two integers. ValueError: The selected qubits are beyond the number of qubits. ValueError: The selected qubits are not natural number. ValueError: The first integer should be less than the second integer. ValueError: The number of selected qubits should be more than 1. ValueError: The selected qubits index are beyond the number of qubits. ValueError: The selected qubits index are not natural number. ValueError: Duplicate selected qubits index are not allowed. ValueError: Invalid input for selected qubits. Returns: dict[int, int]: The mapping of the index of selected qubits to the index of the classical register. """ if selected_qubits is None: return {i: i for i in range(actual_num_qubits)} if isinstance(selected_qubits, int): return { qi: ci for ci, qi in enumerate(range(actual_num_qubits - selected_qubits, actual_num_qubits)) } if isinstance(selected_qubits, tuple): if len(selected_qubits) != 2: raise ValueError( "Subsystem range is defined by only two integers when inputs as tuple, " + f"but there is {len(selected_qubits)} integers in '{selected_qubits}'." ) return qubit_mapper_2_int(actual_num_qubits, selected_qubits) if isinstance(selected_qubits, Sequence): if len(dict.fromkeys(selected_qubits).keys()) != len(selected_qubits): raise ValueError(f"Duplicate selected qubits index {selected_qubits} are not allowed.") selected_qubits = sorted(list(selected_qubits)) all_qubits = list(range(actual_num_qubits)) selected_qubits_actual = [all_qubits[q_idx_raw] for q_idx_raw in selected_qubits] return {qi: ci for ci, qi in enumerate(selected_qubits_actual)} raise ValueError( f"Invalid input for selected qubits: '{selected_qubits}'. " + "It should be 'int', 'tuple[int, int]', or 'list[int]'." )
[docs] def degree_handler( allsystem_size: int, degree: Optional[Union[int, tuple[int, int]]], measure: Optional[tuple[int, int]], ) -> tuple[tuple[int, int], tuple[int, int], int]: """Handle the degree of freedom for the subsystem. Args: allsystem_size (int): The size of the whole system. degree (Optional[Union[int, tuple[int, int]]]): The degree of freedom. measure (Optional[tuple[int, int]]): The measure range. Returns: tuple[tuple[int, int], tuple[int, int], int]: The degree of freedom, measure range, and subsystem size. """ # Determine degree degree = qubit_selector(allsystem_size, degree=degree) subsystem_size = max(degree) - min(degree) # Check whether the bitstring range is valid bitstring_range = degree bitstring_check = { "b > a": (bitstring_range[1] > bitstring_range[0]), "a >= -allsystemSize": bitstring_range[0] >= -allsystem_size, "b <= allsystemSize": bitstring_range[1] <= allsystem_size, "b-a <= allsystemSize": ((bitstring_range[1] - bitstring_range[0]) <= allsystem_size), } if not all(bitstring_check.values()): raise ValueError( f"Invalid 'bitStringRange = {bitstring_range} for allsystemSize = {allsystem_size}'. " + "Available range 'bitStringRange = [a, b)' should be" + ", ".join([f" {k};" for k, v in bitstring_check.items() if not v]) ) if measure is None: measure = qubit_selector(allsystem_size) return bitstring_range, measure, subsystem_size
[docs] def degree_handler_rust( allsystem_size: int, degree: Optional[Union[int, tuple[int, int]]], measure: Optional[tuple[int, int]], ) -> tuple[tuple[int, int], tuple[int, int], int]: """Handle the degree of freedom for the subsystem. Args: allsystem_size (int): The size of the whole system. degree (Optional[Union[int, tuple[int, int]]]): The degree of freedom. measure (Optional[tuple[int, int]]): The measure range. Returns: tuple[tuple[int, int], tuple[int, int], int]: The degree of freedom, measure range, and subsystem size. """ if RUST_AVAILABLE: return degree_handler_rust_source(allsystem_size, degree, measure) warnings.warn( "Rust is not available, using python to calculate degree handler." + f" Check: {FAILED_RUST_IMPORT}", PostProcessingRustUnavailableWarning, ) return degree_handler(allsystem_size, degree, measure)
[docs] def is_cycling_slice_active( allsystem_size: int, bitstring_range: tuple[int, int], subsystem_size: int, ) -> bool: """Check whether the cycling slice is active. Args: allsystem_size (int): The size of the whole system. bitstring_range (tuple[int, int]): The range of the bitstring. subsystem_size (int): The size of the subsystem Returns: bool: Whether the cycling slice is active. """ _dummy_string = list(range(allsystem_size)) _dummy_string_slice = cycling_slice(_dummy_string, bitstring_range[0], bitstring_range[1], 1) is_avtive_cycling_slice = ( _dummy_string[bitstring_range[0] : bitstring_range[1]] != _dummy_string_slice ) if is_avtive_cycling_slice: assert len(_dummy_string_slice) == subsystem_size, ( f"| All system size '{subsystem_size}' " + f"does not match dummyStringSlice '{_dummy_string_slice}'" ) return is_avtive_cycling_slice