from itertools import product
from typing import List, Tuple
import numpy as np
from numpy import pi
from qutip import Qobj, identity, sigmax, sigmay, sigmaz, tensor
from scipy.linalg import pinv
from pyquil import Program
from pyquil.gates import H, X, RX
from quara.interface.qutip.conversion import convert_povm_quara_to_qutip
from quara.objects.povm import Povm
[docs]def generate_program_for_1qubit(qubit: int, state_name: str) -> Program:
"""Generates a pyquil program that prepares the desired for 1 qubit.
Parameters
----------
qubit: int
Index of the target qubit.
state_name: str
Name of the desired state which is z0, x0 or y0.
Returns
-------
Program
Program for state preparation for the specific qubit
"""
if state_name == "z0":
return Program()
elif state_name == "z1":
return Program(X(qubit))
elif state_name == "x0":
return Program(H(qubit))
elif state_name == "x1":
return Program(X(qubit), H(qubit))
elif state_name == "y0":
return Program(RX(-pi / 2, qubit))
elif state_name == "y1":
return Program(RX(pi / 2, qubit))
else:
raise ValueError("invalid state_name")
[docs]def generate_preprocess_program(qubits: List[int], state_name: str) -> Program:
"""Generates a pyquil program for the state preparation of the qubit system.
Parameters
----------
qubits: List[int]
Index configuration of the qubit system.
state_name: str
Name of the desired state.
Returs
------
Program
Program for the state preparation of the system
"""
state_names_1qubit = state_name.split("_")
assert len(qubits) == len(state_names_1qubit)
pre_process_program = Program()
for qubit, state_name_1qubit in zip(qubits, state_names_1qubit):
pre_process_program += generate_program_for_1qubit(qubit, state_name_1qubit)
return pre_process_program
[docs]def calc_empi_dist_from_observables(
observables: List[float], num_shots: int, pauli_strings: List[str], povm: Povm
) -> Tuple[int, List[np.ndarray]]:
"""Calculates the empirical distribution of POVM from observables.
Parameters
----------
observable: List[float]
List of observables measured by the Forest SDK.
num_shots: int
Number of measurement shots performed per observable.
pauli_strings: List[str]
List of strings that expresses a pauli operator which was used in the measurement.
povm: Povm
Quara's povm object which correspond to given pauli_strings.
Returns
-------
Tuple[int, List[np.ndarray]]
Empirical distribution that is compatible with Quara's tomography features.
"""
coefficient_matrix = calc_coefficient_matrix(pauli_strings, povm)
inv_mat, rank = pinv(coefficient_matrix, return_rank=True)
if rank < coefficient_matrix.shape[0]:
raise ValueError(
"Given matrix is not full rank. Some experiments might be missing."
)
return (num_shots, inv_mat @ observables)
[docs]def generate_pauli_strings_from_povm_name(povm_name: str) -> List[str]:
"""Generates Pauli strings from given POVM name to construct a tomographical experiment.
Parameters
----------
povm_name: str
Name of a POVM which is the target of the experiment.
Returns
-------
List[str]
List of Pauli strings for observation that covers sufficient information of the given POVM.
"""
initial_string = "".join(povm_name.split("_")).upper()
allowed_chars = set("XYZ")
assert 0 < len(initial_string)
assert set(initial_string) <= allowed_chars
swap_times = 2 ** len(initial_string)
pauli_strings = [initial_string]
for i in range(1, swap_times):
swap_position = f"{i:0{len(initial_string)}b}"
pauli_str = ""
for c, swap_flag in zip(initial_string, swap_position):
if swap_flag == "1":
pauli_str = pauli_str + "I"
else:
pauli_str = pauli_str + c
pauli_strings.append(pauli_str)
return pauli_strings
[docs]def calc_coefficient_matrix(pauli_strings: List[str], povm: Povm) -> np.ndarray:
"""Calculates coefficient matrix for calculatig empi dist from observables.
Parameters
----------
pauli_strings: List[str]
List of pauli strings which are considered as operators of observables.
povm: Povm
POVM that is the target of generating probability distribution.
Returns
-------
np.ndarray
2d matrix that converts a list of observables to a probability distribution.
"""
coefficient_mat = []
for pauli_string in pauli_strings:
coefficients = calc_coefficients(pauli_string, povm)
coefficient_mat.append(coefficients)
return np.array(coefficient_mat)
[docs]def calc_coefficients(pauli_string: str, povm: Povm) -> List[int]:
"""Calculates a list of coefficients that correspond observables to a probability of a POVM item.
Parameters
----------
pauli_string: str
Pauli string which corresponds to an operator of the obtained expectation.
povm: Povm
POVM that is the targeto of generating probability distribution.
Returns
-------
List[int]
List of coefficients for calculating a probability from Pauli observables.
"""
povm_items = convert_povm_quara_to_qutip(povm)
order_candidates = [list(i) for i in product([1, -1], repeat=len(povm_items))]
target_mat = generate_pauli_operator_from_pauli_string(pauli_string)
dim = target_mat.dims[0][0]
for order in order_candidates:
observable = Qobj(dims=[[dim], [dim]])
for sign, t in zip(order, povm_items):
observable += sign * t
if observable == target_mat:
return order
raise ValueError("Coefficient doesn't exists in given combination")
[docs]def generate_pauli_operator_from_pauli_string(pauli_string: str) -> Qobj:
"""Generates QuTip Qobj from given Pauli string in Forest SDK format.
Parameters
----------
pauli_string: str
A Pauli string which is considered as operator of an observable.
Returns
-------
Qobj
QuTip Qobj that corresponds to the given Pauli string.
"""
pauli_mat_list = []
dim = 1
for pauli_op in pauli_string:
if pauli_op == "X":
pauli_mat_list.append(sigmax())
elif pauli_op == "Y":
pauli_mat_list.append(sigmay())
elif pauli_op == "Z":
pauli_mat_list.append(sigmaz())
elif pauli_op == "I":
pauli_mat_list.append(identity(2))
else:
raise ValueError("Invalid character detected in pauli string.")
dim = dim * 2
return Qobj(tensor(pauli_mat_list), dims=[[dim], [dim]])