"""Post Processing - Randomized Measure - Entangled Entropy - Core 2
(:mod:`qurry.process.randomized_measure.entangled_entropy.entropy_core_2`)
This version introduces another way to process subsystems.
"""
import time
import warnings
from typing import Optional, Iterable
import numpy as np
from .purity_cell_2 import purity_cell_2_py, purity_cell_2_rust
from ...availability import (
availablility,
default_postprocessing_backend,
PostProcessingBackendLabel,
)
from ...exceptions import (
PostProcessingRustImportError,
PostProcessingRustUnavailableWarning,
PostProcessingBackendDeprecatedWarning,
)
from ....tools import ParallelManager, workers_distribution
try:
from ....boorust import randomized # type: ignore
entangled_entropy_core_2_rust_source = randomized.entangled_entropy_core_2_rust
RUST_AVAILABLE = True
FAILED_RUST_IMPORT = None
except ImportError as err:
RUST_AVAILABLE = False
FAILED_RUST_IMPORT = err
def entangled_entropy_core_2_rust_source(*args, **kwargs):
"""Dummy function for entangled_entropy_core_rust."""
raise PostProcessingRustImportError(
"Rust is not available, using python to calculate entangled entropy."
) from FAILED_RUST_IMPORT
BACKEND_AVAILABLE = availablility(
"randomized_measure.entangled_entropy.entropy_core_2",
[
("Rust", RUST_AVAILABLE, FAILED_RUST_IMPORT),
("Cython", "Depr.", None),
],
)
DEFAULT_PROCESS_BACKEND = default_postprocessing_backend(RUST_AVAILABLE, False)
[docs]
def entangled_entropy_core_2_pyrust(
shots: int,
counts: list[dict[str, int]],
selected_classical_registers: Optional[Iterable[int]] = None,
backend: PostProcessingBackendLabel = DEFAULT_PROCESS_BACKEND,
) -> tuple[
dict[int, np.float64],
list[int],
str,
float,
]:
"""The core function of entangled entropy by Python or Rust for just purity cell part.
Args:
shots (int):
Shots of the experiment on quantum machine.
counts (list[dict[str, int]]):
Counts of the experiment on quantum machine.
selected_classical_registers (Optional[Iterable[int]], optional):
The list of **the index of the selected_classical_registers**.
backend (ExistingProcessBackendLabel, optional):
Backend for the process. Defaults to DEFAULT_PROCESS_BACKEND.
Returns:
tuple[dict[int, np.float64], list[int], str, float]:
Purity of each cell, Selected classical registers, Message, Time to calculate.
"""
# check shots
sample_shots = sum(counts[0].values())
assert sample_shots == shots, f"shots {shots} does not match sample_shots {sample_shots}"
# Determine worker number
launch_worker = workers_distribution()
# Determine subsystem size
measured_system_size = len(list(counts[0].keys())[0])
if selected_classical_registers is None:
selected_classical_registers = list(range(measured_system_size))
elif not isinstance(selected_classical_registers, Iterable):
raise ValueError(
"selected_classical_registers should be Iterable, "
+ f"but get {type(selected_classical_registers)}"
)
# dummy_list = range(measured_system_size)
# selected_classical_registers_actual = [
# dummy_list[c_i] for c_i in selected_classical_registers]
# assert len(selected_classical_registers_actual) == len(
# set(selected_classical_registers_actual)
# ), (
# "The selected_classical_registers should not have duplicated values, "
# + f"but get {selected_classical_registers_actual} from {selected_classical_registers}"
# )
# msg = f"| Selected qubits: {selected_classical_registers_actual}"
assert all(
0 <= q_i < measured_system_size for q_i in selected_classical_registers
), f"Invalid selected classical registers: {selected_classical_registers}"
msg = f"| Selected classical registers: {selected_classical_registers}"
begin = time.time()
if backend == "Cython":
warnings.warn(
f"Cython is deprecated, using {DEFAULT_PROCESS_BACKEND} to calculate purity cell.",
PostProcessingBackendDeprecatedWarning,
)
backend = DEFAULT_PROCESS_BACKEND
cell_calculation = purity_cell_2_rust if backend == "Rust" else purity_cell_2_py
pool = ParallelManager(launch_worker)
purity_cell_result_list = pool.starmap(
cell_calculation,
[(i, c, selected_classical_registers) for i, c in enumerate(counts)],
)
taken = round(time.time() - begin, 3)
selected_classical_registers_sorted = sorted(selected_classical_registers, reverse=True)
purity_cell_dict: dict[int, np.float64] = {}
selected_classical_registers_checked: dict[int, bool] = {}
for (
idx,
purity_cell_value,
selected_classical_registers_sorted_result,
) in purity_cell_result_list:
purity_cell_dict[idx] = purity_cell_value
if selected_classical_registers_sorted_result != selected_classical_registers_sorted:
selected_classical_registers_checked[idx] = False
if len(selected_classical_registers_checked) > 0:
warnings.warn(
"Selected qubits are not sorted for "
+ f"{len(selected_classical_registers_checked)} cells.",
RuntimeWarning,
)
return purity_cell_dict, selected_classical_registers_sorted, msg, taken
[docs]
def entangled_entropy_core_2_allrust(
shots: int,
counts: list[dict[str, int]],
selected_classical_registers: Optional[Iterable[int]] = None,
) -> tuple[
dict[int, np.float64],
list[int],
str,
float,
]:
"""The core function of entangled entropy by Rust for just purity cell part.
Args:
shots (int):
Shots of the experiment on quantum machine.
counts (list[dict[str, int]]):
Counts of the experiment on quantum machine.
selected_classical_registers (Optional[Iterable[int]], optional):
The list of **the index of the selected_classical_registers**.
Returns:
tuple[dict[int, np.float64], list[int], str, float]:
Purity of each cell, Selected qubits, Message, Time to calculate.
"""
return entangled_entropy_core_2_rust_source(
shots,
counts,
(
selected_classical_registers
if selected_classical_registers is None
else list(selected_classical_registers)
),
)
[docs]
def entangled_entropy_core_2(
shots: int,
counts: list[dict[str, int]],
selected_classical_registers: Optional[Iterable[int]] = None,
backend: PostProcessingBackendLabel = DEFAULT_PROCESS_BACKEND,
) -> tuple[
dict[int, np.float64],
list[int],
str,
float,
]:
"""The core function of entangled entropy.
Args:
shots (int):
Shots of the experiment on quantum machine.
counts (list[dict[str, int]]):
Counts of the experiment on quantum machine.
selected_classical_registers (Optional[Iterable[int]], optional):
The list of **the index of the selected_classical_registers**.
backend (ExistingProcessBackendLabel, optional):
Backend for the process. Defaults to DEFAULT_PROCESS_BACKEND.
Returns:
tuple[dict[int, np.float64], list[int], str, float]:
Purity of each cell, Selected qubits, Message, Time to calculate.
"""
if backend == "Cython":
warnings.warn(
f"The Cython is deprecated, using {DEFAULT_PROCESS_BACKEND} to calculate purity cell.",
PostProcessingBackendDeprecatedWarning,
)
backend = DEFAULT_PROCESS_BACKEND
if backend == "Rust":
if RUST_AVAILABLE:
return entangled_entropy_core_2_allrust(shots, counts, selected_classical_registers)
warnings.warn(
"Rust is not available, using Python to calculate purity cell."
+ f"Check the error: {FAILED_RUST_IMPORT}",
PostProcessingRustUnavailableWarning,
)
backend = "Python"
return entangled_entropy_core_2_pyrust(shots, counts, selected_classical_registers, backend)