"""Post Processing - Classical Shadow - Rho M K Cell
(:mod:`qurry.process.classical_shadow.rho_mk_cell`)
"""
from typing import Literal, Union, Any
import numpy as np
from .unitary_set import U_M_MATRIX, IDENTITY, OUTER_PRODUCT
from .matrix_calcution import rho_mki_kronecker_product_numpy
from ..utils import single_counts_recount_pyrust
# pylint: disable=invalid-name
RhoMKCellMethod = Union[Literal["numpy", "numpy_precomputed"], str]
"""Type for rho_mk_cell method.
It can be either "numpy", "numpy_precomputed".
- "numpy": Use Numpy to calculate the rho_m.
- "numpy_precomputed": Use Numpy to calculate the rho_m with precomputed values.
Currently, "numpy_precomputed" is the best option for performance.
"""
# pylint: enable=invalid-name
[docs]
def rho_mk_cell_py(
idx: int,
single_counts: dict[str, int],
nu_shadow_direction: dict[int, Union[Literal[0, 1, 2], int]],
selected_classical_registers: list[int],
) -> tuple[
int,
list[tuple[str, int, np.ndarray[tuple[int, int], np.dtype[np.complex128]]]],
list[int],
]:
r""":math:`\rho_{mk}` calculation for single cell.
The matrix :math:`\rho_{mk}^{i}` is calculated by the following equation,
.. math::
\rho_{mk}^{i} = \frac{3} U_{mi}^{\dagger} |b_k \rangle\langle b_k | U_{mi} - \mathbb{1}
The matrix :math:`\rho_{mk}` is calculated by the following equation,
.. math::
\rho_{mk} = \bigotimes_{i=1}^{N_q} \rho_{mk}^{i}
where :math:`N_q` is the number of qubits,
Args:
idx (int):
Index of the cell (counts).
single_counts (dict[str, int]):
Counts measured by the single quantum circuit.
nu_shadow_direction (dict[int, Union[Literal[0, 1, 2], int]]):
The shadow direction of the unitary operators.
selected_classical_registers (list[int]):
The list of **the index of the selected_classical_registers**.
Returns:
tuple[
int,
list[tuple[str, int, np.ndarray[tuple[int, int], np.dtype[np.complex128]]]]
list[int],
]:
Index, the list of the data of rho_mk, the sorted list of the selected qubits
"""
# subsystem making
num_classical_register = len(list(single_counts.keys())[0])
selected_classical_registers_sorted = sorted(selected_classical_registers, reverse=True)
single_counts_under_degree = single_counts_recount_pyrust(
single_counts, num_classical_register, selected_classical_registers_sorted
)
# core calculation
rho_m_k_data: list[tuple[str, int, np.ndarray[tuple[int, int], np.dtype[np.complex128]]]] = []
for bitstring, num_bitstring in single_counts_under_degree.items():
tmp_dict = {
q_di: (
3
* U_M_MATRIX[nu_shadow_direction[q_di]].conj().T
[docs]
@ OUTER_PRODUCT[s_q]
@ U_M_MATRIX[nu_shadow_direction[q_di]]
)
- IDENTITY
for q_di, s_q in zip(selected_classical_registers_sorted, bitstring)
}
tmp: Any = tmp_dict[selected_classical_registers_sorted[0]]
for q_di in selected_classical_registers_sorted[1:]:
tmp = np.kron(tmp, tmp_dict[q_di])
rho_m_k_data.append((bitstring, num_bitstring, tmp))
return idx, rho_m_k_data, selected_classical_registers_sorted
def rho_mk_cell_py_precomputed(
idx: int,
single_counts: dict[str, int],
nu_shadow_direction: dict[int, Union[Literal[0, 1, 2], int]],
selected_classical_registers: list[int],
) -> tuple[
int,
list[tuple[str, int, np.ndarray[tuple[int, int], np.dtype[np.complex128]]]],
list[int],
]:
r""":math:`\rho_{mk}` calculation for single cell.
The matrix :math:`\rho_{mk}^{i}` is calculated by the following equation,
.. math::
\rho_{mk}^{i} = \frac{3} U_{mi}^{\dagger} |b_k \rangle\langle b_k | U_{mi} - \mathbb{1}
The matrix :math:`\rho_{mk}` is calculated by the following equation,
.. math::
\rho_{mk} = \bigotimes_{i=1}^{N_q} \rho_{mk}^{i}
where :math:`N_q` is the number of qubits,
Args:
idx (int):
Index of the cell (counts).
single_counts (dict[str, int]):
Counts measured by the single quantum circuit.
nu_shadow_direction (dict[int, Union[Literal[0, 1, 2], int]]):
The shadow direction of the unitary operators.
selected_classical_registers (list[int]):
The list of **the index of the selected_classical_registers**.
Returns:
tuple[
int,
list[tuple[str, int, np.ndarray[tuple[int, int], np.dtype[np.complex128]]]]
list[int],
]:
Index, the list of the data of rho_mk, the sorted list of the selected qubits
"""
# subsystem making
num_classical_register = len(list(single_counts.keys())[0])
selected_classical_registers_sorted = sorted(selected_classical_registers, reverse=True)
single_counts_under_degree = single_counts_recount_pyrust(
single_counts, num_classical_register, selected_classical_registers_sorted
)
# core calculation
rho_m_k_data: list[tuple[str, int, np.ndarray[tuple[int, int], np.dtype[np.complex128]]]] = []
for bitstring, num_bitstring in single_counts_under_degree.items():
tmp = rho_mki_kronecker_product_numpy(
[
(nu_shadow_direction[q_di], s_q)
for q_di, s_q in zip(selected_classical_registers_sorted, bitstring)
]
)
rho_m_k_data.append((bitstring, num_bitstring, tmp))
return idx, rho_m_k_data, selected_classical_registers_sorted