import itertools
from typing import List, Tuple, Union
import numpy as np
from quara.objects.state import State, convert_var_to_state
from quara.objects.povm import Povm
from quara.objects.qoperation import QOperation
from quara.objects.qoperations import SetQOperations
from quara.protocol.qtomography.standard.standard_qtomography import StandardQTomography
from quara.qcircuit.experiment import Experiment
[docs]class StandardQst(StandardQTomography):
_estimated_qoperation_type = State
def __init__(
self,
povms: List[Povm],
is_physicality_required: bool = False,
is_estimation_object: bool = False,
on_para_eq_constraint: bool = False,
eps_proj_physical: float = None,
seed: int = None,
schedules: Union[str, List[List[Tuple]]] = "all",
):
"""Constructor
Parameters
----------
povms : List[Povm]
testers of QST.
is_physicality_required : bool, optional
whether the QOperation is physically required, by default False
is_estimation_object : bool, optional
whether the QOperation is estimation object, by default False
on_para_eq_constraint : bool, optional
whether the parameters of QOperation are on equal constraint, by default False
eps_proj_physical : float, optional
threshold epsilon where the algorithm repeats the projection in order to make estimate object is physical, by default :func:`~quara.settings.Settings.get_atol` / 10.0
seed : int, optional
a seed used to generate random data, by default None.
Raises
------
ValueError
the experiment is not valid.
"""
# create Experiment
if type(schedules) == str:
self._validate_schedules_str(schedules)
if schedules == "all":
schedules = [[("state", 0), ("povm", i)] for i in range(len(povms))]
experiment = Experiment(
states=[None], gates=[], povms=povms, schedules=schedules, seed=seed
)
self._validate_schedules(schedules)
# create SetQOperations
state = State(
povms[0]._composite_system,
np.zeros(povms[0].vecs[0].shape, dtype=np.float64),
is_physicality_required=is_physicality_required,
is_estimation_object=is_estimation_object,
on_para_eq_constraint=on_para_eq_constraint,
eps_proj_physical=eps_proj_physical,
)
set_qoperations = SetQOperations(states=[state], gates=[], povms=[])
# initialize super class
super().__init__(experiment, set_qoperations)
# validate
if not self.is_valid_experiment():
raise ValueError(
"the experiment is not valid. all CompositeSystem of testers must have same ElementalSystems."
)
# calc num_variables
if on_para_eq_constraint:
self._num_variables = state.dim ** 2 - 1
else:
self._num_variables = state.dim ** 2
# create map
self._map_experiment_to_setqoperations = {("state", 0): ("state", 0)}
self._map_setqoperations_to_experiment = {("state", 0): ("state", 0)}
# calc and set coeff0s, coeff1s, matA and vecB
self._set_coeffs(experiment, on_para_eq_constraint, state.dim)
self._on_para_eq_constraint = on_para_eq_constraint
def _validate_schedules(self, schedules):
for i, schedule in enumerate(schedules):
if schedule[0][0] != "state" or schedule[1][0] != "povm":
message = f"schedules[{i}] is invalid. "
message += 'Schedule of Qst must be in format as \'[("state", 0), ("povm", povm_index)]\', '
message += f"not '{schedule}'."
raise ValueError(message)
if schedule[0][1] != 0:
message = f"schedules[{i}] is invalid."
message += f"State index of schedule in Qst must be 0: {schedule}"
raise ValueError(message)
@property
def on_para_eq_constraint(self): # read only
return self._on_para_eq_constraint
[docs] def estimation_object_type(self) -> type:
return State
def _set_coeffs(
self, experiment: Experiment, on_para_eq_constraint: bool, dim: int
):
# coeff0s and coeff1s
self._coeffs_0th = dict()
self._coeffs_1st = dict()
tmp_coeffs_0th = []
tmp_coeffs_1st = []
for schedule_index, schedule in enumerate(self._experiment.schedules):
povm_index = schedule[-1][1]
povm = self._experiment.povms[povm_index]
for element_index, vec in enumerate(povm.vecs):
if on_para_eq_constraint:
self._coeffs_0th[(schedule_index, element_index)] = vec[
0
] / np.sqrt(dim)
self._coeffs_1st[(schedule_index, element_index)] = vec[1:]
tmp_coeffs_0th.append(vec[0])
tmp_coeffs_1st.append(vec[1:])
else:
self._coeffs_0th[(schedule_index, element_index)] = 0
self._coeffs_1st[(schedule_index, element_index)] = vec
tmp_coeffs_0th.append(0)
tmp_coeffs_1st.append(vec)
[docs] def is_valid_experiment(self) -> bool:
"""returns whether the experiment is valid.
all of the following conditions are ``True``, the state is physically correct:
- all povms have same CompositeSystem.
Returns
-------
bool
whether the experiment is valid.
"""
povms = self._experiment.povms
checks = [
povms[0]._composite_system == povm._composite_system for povm in povms[1:]
]
return all(checks)
def _get_target_index(self, experiment: Experiment, schedule_index: int) -> int:
schedule = experiment.schedules[schedule_index]
state_index = schedule[0][1]
return state_index
[docs] def generate_dataset(self, data_nums: List[int]) -> List[List[np.ndarray]]:
"""calculates a probability distribution.
see :func:`~quara.protocol.qtomography.qtomography.QTomography.generate_dataset`
"""
raise NotImplementedError()
[docs] def generate_empi_dist(
self, schedule_index: int, state: State, num_sum: int
) -> Tuple[int, np.ndarray]:
"""Generate empirical distribution using the data generated from probability distribution of specified schedules.
Parameters
----------
schedule_index : int
schedule index.
state : State
true object.
num_sum : int
the number of data to use to generate the experience distributions for each schedule.
Returns
-------
Tuple[int, np.ndarray]
Generated empirical distribution.
"""
tmp_experiment = self._experiment.copy()
state_index = self._get_target_index(tmp_experiment, schedule_index)
tmp_experiment.states[state_index] = state
empi_dist_seq = tmp_experiment.generate_empi_dist_sequence(
schedule_index, [num_sum]
)
return empi_dist_seq[0]
[docs] def generate_empi_dists(
self, state: State, num_sum: int
) -> List[Tuple[int, np.ndarray]]:
"""Generate empirical distributions using the data generated from probability distributions of all schedules.
see :func:`~quara.protocol.qtomography.qtomography.QTomography.generate_empi_dists`
"""
tmp_experiment = self._experiment.copy()
for schedule_index in range(len(tmp_experiment.schedules)):
state_index = self._get_target_index(tmp_experiment, schedule_index)
tmp_experiment.states[state_index] = state
num_sums = [num_sum] * self._num_schedules
empi_dist_seq = tmp_experiment.generate_empi_dists_sequence([num_sums])
empi_dists = list(itertools.chain.from_iterable(empi_dist_seq))
return empi_dists
[docs] def generate_empi_dists_sequence(
self, state: State, num_sums: List[int]
) -> List[List[Tuple[int, np.ndarray]]]:
"""Generate sequence of empirical distributions using the data generated from probability distributions of all schedules.
Parameters
----------
state : State
true object.
num_sums : List[int]
list of the number of data to use to generate the experience distributions for each schedule.
Returns
-------
List[List[Tuple[int, np.ndarray]]]
sequence of list of tuples for the number of data and experience distributions for each schedules.
"""
tmp_experiment = self._experiment.copy()
list_num_sums = [num_sums] * self._num_schedules
list_num_sums_tmp = [list(num_sums) for num_sums in zip(*list_num_sums)]
for schedule_index in range(len(tmp_experiment.schedules)):
state_index = self._get_target_index(tmp_experiment, schedule_index)
tmp_experiment.states[state_index] = state
empi_dists_sequence_tmp = tmp_experiment.generate_empi_dists_sequence(
list_num_sums_tmp
)
empi_dists_sequence = [
list(empi_dists) for empi_dists in zip(*empi_dists_sequence_tmp)
]
return empi_dists_sequence
[docs] def convert_var_to_qoperation(self, var: np.ndarray) -> State:
"""converts variable to QOperation.
see :func:`~quara.protocol.qtomography.standard.standard_qtomography.StandardQTomography.convert_var_to_qoperation`
"""
template = self._set_qoperations.states[0]
state = template.generate_from_var(var=var)
return state
[docs] def generate_empty_estimation_obj_with_setting_info(self) -> QOperation:
"""generates the empty estimation object with setting information.
Returns
-------
QOperation
the empty estimation object(State) with setting information.
"""
empty_estimation_obj = self._set_qoperations.states[0]
return empty_estimation_obj.copy()
[docs] def num_outcomes(self, schedule_index: int) -> int:
"""returns the number of outcomes of probability distribution of a schedule index.
Parameters
----------
schedule_index: int
Returns
-------
int
the number of outcomes
"""
assert schedule_index >= 0
assert schedule_index < self.num_schedules
povm_index = self._experiment.schedules[schedule_index][1][1]
return len(self._experiment._povms[povm_index].vecs)