Source code for qolumbina.utils.data_conversion

"""
A collection of utility functions that can be used for preprocessing 
and postprocessing of the test process.
"""

from qiskit.quantum_info import Statevector, partial_trace, DensityMatrix
import numpy as np

[docs] def int_to_gate_list(value: int, n_bits: int) -> list[str]: """ Convert a decimal integer to gate list representation of a quantum state using binary encoding. Args: value: Non-negative integer to convert n_bits: Number of bits in the output gate list Returns: Representation of a gate list Raises: ValueError: if ``value`` is negative, or if ``n_bits`` is not positive TypeError: if ``value`` is not an integer, or if ``n_bits`` is not an integer Example: >>> int_to_gate_list(3, 4) ['x', 'x', 'i', 'i'] # 3 = 0b0011 # (q0, q1, q2, q3) -> ('x', 'x', 'i', 'i') """ if value < 0: raise ValueError("value must be non-negative") if not isinstance(n_bits, int) or n_bits <= 0: raise ValueError("n_bits must be a positive integer") if not isinstance(value, int): raise TypeError("value must be an integer") gates = [] for i in range(n_bits): bit = (value >> i) & 1 gates.append("x" if bit == 1 else "i") return gates
[docs] def frac_to_gate_list(value: float, n_bits: int, tol: float = 1e-9) -> list[str]: r""" Convert a fractional decimal number to a quantum gate list using fixed-point binary representation, with precision check. Args: value: Fractional decimal number to convert (must be in :math:`[0, 1)`) n_bits: Number of bits in the output gate list (must be positive) tol: Tolerance for precision check (default: 1e-9) Returns: Representation of a gate list Raises: ValueError: if ``value`` is not in :math:`[0, 1)`, or if ``n_bits`` is not positive TypeError: if ``value`` is not a float, or if ``n_bits`` is not an integer Example: >>> frac_to_gate_list(0.375, 4) # 0.25 + 0.125 = 0.011 in binary ['i', 'x', 'x', 'i'] """ if not (0 <= value < 1): raise ValueError("value must be in [0, 1)") if n_bits <= 0: raise ValueError("n_bits must be positive") gates = [] remaining = value for i in range(1, n_bits + 1): weight = 2 ** (-i) if remaining >= weight: gates.append("x") remaining -= weight else: gates.append("i") # Precision check if remaining > tol: raise ValueError( f"Precision loss too large: remaining={remaining}, tol={tol}" ) return gates
[docs] def counts_to_probabilities(counts: dict[str, int]) -> dict[str, float]: """ Convert counts to probabilities. Args: counts: Measurement counts as a dictionary mapping bitstrings to their counts Returns: A dictionary mapping bitstrings to their probabilities. Example: >>> counts_to_probabilities({'00': 500, '01': 300, '10': 100, '11': 100}) {'00': 0.5, '01': 0.3, '10': 0.1, '11': 0.1} """ probabilities = {} shots = sum(counts.values()) for bitstring, count in counts.items(): probabilities[bitstring] = count / shots return probabilities
[docs] def counts_to_expectation( counts: dict[str, int], observable: dict[str, float] ) -> float: """ Convert counts to expectation value of a given observable. Args: counts: Measurement counts as a dictionary mapping bitstrings to their counts observable: A dictionary mapping bitstrings to their observable values Returns: The expectation value as a float. Example: >>> counts_to_expectation({'00': 500, '01': 300, '10': 100, '11': 100}, ... {'00': 1.0, '01': -1.0, '10': -1.0, '11': 1.0}) 0.2 # (0.5*1.0 + 0.3*(-1.0) + 0.1*(-1.0) + 0.1*1.0 = 0.2) """ expectation = 0.0 shots = sum(counts.values()) for bitstring, count in counts.items(): prob = count / shots obs_value = observable.get(bitstring, 0.0) expectation += prob * obs_value return expectation
[docs] def decompose_joint_state( statevector: Statevector, target_subsystem: list[int], atol: float = 1e-10, ) -> tuple[str, Statevector | DensityMatrix]: r""" Return the reduced density matrix of the given subsystem. Args: statevector: Joint pure state :math:`\ket{\psi_{AB}}` of the entire system consisting of subsystems :math:`A` and :math:`B`, represented as a Statevector object. target_subsystem: List of qubit indices for the target subsystem :math:`A` atol: Absolute tolerance for numerical operations Returns: A tuple containing a string indicating the type (``'pure_state'`` or ``'mixed_state'``) and the corresponding ``Statevector`` or ``DensityMatrix`` of the target subsystem :math:`A`. Raises: ValueError: if ``target_subsystem`` indices exceed the number of qubits in statevector Example: >>> from qiskit.quantum_info import Statevector >>> import numpy as np >>> psi_AB = Statevector.from_label('00') + Statevector.from_label('11') >>> psi_AB = psi_AB / np.linalg.norm(psi_AB.data) # Normalize >>> decompose_joint_state(psi_AB, target_subsystem=[0]) ('mixed_state', DensityMatrix([[0.5+0.j, 0. +0.j], [0. +0.j, 0.5+0.j]], dims=(2,))) """ num_qubits: int = statevector.num_qubits if statevector.num_qubits is not None else 0 if num_qubits <= max(target_subsystem): raise ValueError("target_subsystem indices exceed number of qubits in statevector") # The qubits to be traced out trace_out = [i for i in range(num_qubits) if i not in target_subsystem] # Partial trace rho_subsystem = partial_trace(statevector, trace_out) # Determine whether it is in a pure state if abs(rho_subsystem.purity() - 1.0) < atol: # Extract the state vector of the pure state # For a pure state, rho = |psi><psi|, the largest eigenvalue is 1 eigenvals, eigenvecs = np.linalg.eigh(rho_subsystem.data) psi = eigenvecs[:, np.argmax(eigenvals)] return "pure_state", Statevector(psi) # Mixed state: directly return the density matrix return "mixed_state", DensityMatrix(rho_subsystem)