Source code for qulearn.utils

"""Frequently used functions."""

import math
from itertools import chain, combinations
from typing import Dict, List, Tuple

from .types import Observable, Tensor


[docs] def probabilities_to_dictionary(probs: Tensor) -> Dict[str, Tensor]: """ Convert 1D tensor of probabilities to dictionary of probabilities of bitstrings. :param probs: Probabilities. :type probs: Tensor :return: Probabilities of bitstrings. :rtype: Dict :raises ValueError: If length of probs is not a power of 2. """ n = int(math.log2(len(probs))) if 2**n != len(probs): raise ValueError(f"Length of probs ({len(probs)}) is not a power of 2.") result = {} for i, p in enumerate(probs): bistring = bin(i)[2:].zfill(n) result[bistring] = p return result
[docs] def samples_to_dictionary(samples: Tensor) -> Dict[str, float]: """Convert 2D tensor of samples to dictionary of probabilities of bitstrings. :param samples: Samples. :type samples: Tensor :return: Probabilities of bitstrings. :rtype: Dict :raises ValueError: If samples not a Tensor of integers. """ if samples.is_floating_point() or samples.is_complex(): raise ValueError("Samples must be tensors of integers.") bitstrings = ["".join(str(b.item()) for b in sample) for sample in samples] bitstring_counts = {bs: bitstrings.count(bs) for bs in set(bitstrings)} total = sum(bitstring_counts.values()) bitstring_probs = {bs: count / total for bs, count in bitstring_counts.items()} return bitstring_probs
[docs] def all_bin_sequences(n: int) -> List[Tuple[int, ...]]: """ Generates all possible binary sequences of length n. :param n: The length of the binary sequences. :type n: int :return: A list of all binary sequences of length n, represented as a list of integers. :rtype: List[List[int]] """ elements = list(range(n)) return list(chain.from_iterable(combinations(elements, r) for r in range(n + 1)))
[docs] def parities_outcome(bitstring: str, H: Observable) -> float: """ Compute the measurement outcome for a given bit string and Hamiltonian. Only works for Hamiltonians with identity and Pauli Z. :param bitstring: Input bit string. :type bitstring: string: str :param H: Hamiltonian. :type H: Observable :return: Real-valued outcome. :rtype: float :raises ValueError: If number of qubits does not match or operators other than I or Z are detected. """ num_qubits = len(bitstring) num_wires = len(H.wires) if num_qubits != num_wires: raise ValueError( f"Number of qubits ({num_qubits}) " f"does not match number of wires ({num_wires})" ) sum = 0.0 for idx, O in enumerate(H.ops): if not isinstance(O.name, list): if O.name == "Identity": sum += H.coeffs[idx] elif O.name == "PauliZ": i = O.wires[0] sign = (-1) ** (int(bitstring[-1 - i])) sum += sign * H.coeffs[idx] else: raise ValueError("All operators must be PauliZ or Identity.") else: if not all(name == "PauliZ" for name in O.name): raise ValueError("All operators must be PauliZ or Identity.") sign = 1 for w in O.wires: sign *= (-1) ** (int(bitstring[-1 - w])) sum += sign * H.coeffs[idx] return sum
[docs] def parities_outcome_probs(probs: Dict[str, float], H: Observable) -> Dict[float, float]: """ Compute (real-valued) outputs with corresponding probabilities. :param probs: Dictionary of probabilities of bitstrings. :type probs: Dict :param H: Hamiltonian determening the outcome. :type H: Observable :return: Values with corresponding probabilities. :rtype: Dict """ result = {} for b, p in probs.items(): val = parities_outcome(b, H) result[val] = p return result