Source code for quara.objects.circuit

from typing import List, Union

from quara.interface.qulacs.conversion import (
    convert_gate_quara_to_qulacs,
    convert_instrument_quara_to_qulacs,
    convert_state_quara_to_qulacs,
    get_index_tuple_from_kraus_matrices_indices,

)
from quara.objects.composite_system_typical import generate_composite_system
from quara.objects.gate import Gate

from quara.objects.gate_typical import (
    generate_gate_from_gate_name,
    get_gate_names_1qubit,
    get_gate_names_1qutrit,
    get_gate_names_2qubit,
    get_gate_names_2qutrit,
    get_gate_names_3qubit,
)
from quara.objects.mprocess import MProcess
from quara.objects.mprocess_typical import generate_mprocess_from_name
from quara.objects.multinomial_distribution import MultinomialDistribution
from quara.objects.state import State
from quara.objects.state_typical import generate_state_from_name

from qulacs import state as qulacs_state
from qulacs import DensityMatrix

import numpy as np

[docs]class CircuitResult: """ Class to manage experiment settings This class is not limited to tomography, but deals with general quantum circuits. """ def __init__(self, circuit_overview: List[dict]): """Constructor Parameters ---------- circuit_overview : List[dict] Overview of a quantum circuit which contains dictionaries of quantum objects. see Circuit class for more information. """ self._raw_result: List[int] = [] self._empi_dists: Union[List[MultinomialDistribution], None] = None self._circuit_overview: List[dict] = circuit_overview
[docs] def append_raw_output(self, raw_output: List[int]): """Appends raw output of a quantum circuit into an internal array. Parameters ---------- raw_output : List[int] List of readouts of the measurements in a circuit. Returns ------- None """ self._raw_result.append(raw_output)
@property def empi_dists(self): return self._empi_dists @property def raw_result(self): return self._raw_result
[docs] def calc_empi_dist(self) -> List[MultinomialDistribution]: """Calculates empirical distributions based on raw readout data for every measurement operator in a quantum circuit. Parameters ---------- None Returns ------- List[MultinomialDistribution] Number of distributions match to the number of measurement operators in a quantum circuit. """ counts = [[0 for _ in qobj["KrausMatrixIndices"]] for qobj in self._circuit_overview if qobj["Type"]=="MProcess"] for raw_data in self._raw_result: for i, outcome in enumerate(raw_data): counts[i][outcome] += 1 empi_dists = [np.array(count, dtype=np.float64)/sum(count) for count in counts] self._empi_dists = [ MultinomialDistribution(ps) for ps in empi_dists ] return self._empi_dists
def __str__(self): desc = f"Type:\n{self.__class__.__name__}\n\n" desc += f"Circuit Overview:\n[\n" for qobject in self._circuit_overview: desc += f" {qobject.__str__()},\n" desc += "]\n\n" if self._empi_dists: desc += "Empirical distributions:\n[\n" for empi_dist in self._empi_dists: desc += f" {empi_dist.ps},\n" desc += f"]\n" desc = desc.rstrip("\n") return desc
[docs]class Circuit(object): def __init__(self, num: int, mode: str = "qubit"): """Constructor Parameters ---------- num : int number of qubits in a quantum circuit. mode: currently supports only "qubit". """ self._num = num self._mode = mode self._qobjects: List[dict] = [] def _generate_gate_from_name(self, gate_name: str, ids: List[int]) -> Gate: gate_q_num = None if gate_name in get_gate_names_1qubit(): gate_q_num = 1 elif gate_name in get_gate_names_2qubit(): gate_q_num = 2 elif gate_name in get_gate_names_3qubit(): gate_q_num = 3 elif gate_name in get_gate_names_1qutrit(): gate_q_num = 1 elif gate_name in get_gate_names_2qutrit(): gate_q_num = 2 else: raise ValueError(f"No such gate_name: {gate_name}") c_sys = generate_composite_system(self._mode, gate_q_num) return generate_gate_from_gate_name(gate_name, c_sys, ids) def _calc_qulacs_target_ids(self, ids: List[int]) -> List[int]: if self._mode == "qubit": return [self._num - 1 - i for i in ids[::-1]] elif self._mode == "qutrit": # add padding because qutrit system is embeded into qubit system. # double the number of target indices with dummy targets target_ids = [] for i in ids[::-1]: target_ids.append(2*self._num - 1 - 2*i) target_ids.append(2*self._num - 2 - 2*i) return target_ids
[docs] def add_gate( self, ids: List[int], gate: Union[Gate, None] = None, gate_name: Union[str, None] = None, ) -> None: """Adds a quantum gate to the circuit. Parameters ---------- ids: List[int] List of indices of qubits which the gate will be applied on. gate : Gate optional. Either gate or gate_name must be specified. A gate object that will be added to a circuit. gate_name : str optional. Either gate or gate_name must be specified. A name of a gate object that will be added to a circuit. Valid gate names can be listed by calling get_gate_names(). Returns ------- List[MultinomialDistribution] Number of distributions match to the number of measurement operators in a quantum circuit. """ qulacs_target_ids = self._calc_qulacs_target_ids(ids) quara_ids = [i for i in range(len(ids))] if gate_name is None and gate is None: raise ValueError("Either gate_name or gate must be defined.") if gate_name is not None and gate is not None: raise ValueError( "You can't define both gate_name and gate. Define either one of these arguments." ) if gate_name: gate = self._generate_gate_from_name(gate_name, quara_ids) if self._mode == "qutrit": c_sys = generate_composite_system("qubit", 2*len(ids)) gate = Gate.embed_qoperation_from_qutrits_to_qubits(gate, list(c_sys.elemental_systems)) qulacs_gate = convert_gate_quara_to_qulacs(gate, qulacs_target_ids) self._qobjects.append( { "Type": "Gate", "QObject": qulacs_gate, "TargetIds": ids, "Name": gate_name, } )
def _generate_mprocess_from_name( self, mprocess_name: str, ids=List[int] ) -> MProcess: c_sys = generate_composite_system("qubit", len(ids)) return generate_mprocess_from_name(c_sys, mprocess_name)
[docs] def add_mprocess( self, ids: List[int], mprocess: Union[MProcess, None] = None, mprocess_name: Union[str, None] = None, ) -> None: """Adds a mprocess to the circuit. Parameters ---------- ids: List[int] List of indices of qubits which the measurement will be applied on. mprocess : MProcess optional. Either mprocess or mprocess_name must be specified. A mprocess object that will be added to a circuit. mprocess_name : str optional. Either mprocess or mprocess_name must be specified. A name of a mprocess that will be added to a circuit. Valid mprocess names can be listed by calling get_mprocess_names_type1() and get_mprocess_names_type2(). Returns ------- List[MultinomialDistribution] Number of distributions match to the number of measurement operators in a quantum circuit. """ qulacs_target_ids = self._calc_qulacs_target_ids(ids) if mprocess_name is None and mprocess is None: raise ValueError("Either mprocess_name or mprocess must be defined.") if mprocess_name is not None and mprocess is not None: raise ValueError( "You can't define both mprocess_name and mprocess. Define either one of these arguments." ) if mprocess_name: mprocess = self._generate_mprocess_from_name(mprocess_name, ids) if self._mode == "qutrit": c_sys = generate_composite_system("qubit", 2*len(ids)) mprocess = MProcess.embed_qoperation_from_qutrits_to_qubits(mprocess, list(c_sys.elemental_systems)) qulacs_instrument, qulacs_ids = convert_instrument_quara_to_qulacs( mprocess, qulacs_target_ids ) self._qobjects.append( { "Type": "MProcess", "QObject": qulacs_instrument, "TargetIds": ids, "KrausMatrixIndices": qulacs_ids, "Name": mprocess_name, } )
def _generate_initial_state_from_states_qubit(self, initial_states: List[State]) -> DensityMatrix: qulacs_density_mat = convert_state_quara_to_qulacs(initial_states[-1]) # the order of qubits are opposite between quara and qulacs for state in initial_states[-2::-1]: tmp_mat = convert_state_quara_to_qulacs(state) qulacs_density_mat = qulacs_state.tensor_product( tmp_mat, qulacs_density_mat ) return qulacs_density_mat def _generate_initial_state_from_states_qutrit(self, initial_states: List[State]) -> DensityMatrix: c_sys = generate_composite_system("qubit", 2) state_tmp = State.embed_qoperation_from_qutrits_to_qubits(initial_states[-1], list(c_sys.elemental_systems)) qulacs_density_mat = convert_state_quara_to_qulacs(state_tmp) # the order of qubits are opposite between quara and qulacs for state in initial_states[-2::-1]: tmp_state = State.embed_qoperation_from_qutrits_to_qubits(state, list(c_sys.elemental_systems)) tmp_mat = convert_state_quara_to_qulacs(tmp_state) qulacs_density_mat = qulacs_state.tensor_product( tmp_mat, qulacs_density_mat ) return qulacs_density_mat def _generate_initial_state_from_states(self, initial_states: List[State]) -> DensityMatrix: if self._mode == "qubit": return self._generate_initial_state_from_states_qubit(initial_states) elif self._mode == "qutrit": return self._generate_initial_state_from_states_qutrit(initial_states) def _generate_initial_state_from_name_qutrit(self, initial_state_mode: str = "all_zero") -> DensityMatrix: states = [] c_sys = generate_composite_system("qutrit", 1) c_sys_qubit = generate_composite_system("qubit", 2) if initial_state_mode == "all_zero": for _ in range(self._num): tmp_state = generate_state_from_name(c_sys, "01z0") states.append(State.embed_qoperation_from_qutrits_to_qubits(tmp_state, list(c_sys_qubit.elemental_systems))) qulacs_density_mat = convert_state_quara_to_qulacs(states[0]) for state in states[1::]: tmp_mat = convert_state_quara_to_qulacs(state) qulacs_density_mat = qulacs_state.tensor_product( tmp_mat, qulacs_density_mat ) return qulacs_density_mat def _generate_initial_state_from_name_qubit(self, initial_state_mode: str = "all_zero") -> DensityMatrix: states = [] c_sys = generate_composite_system("qubit", 1) if initial_state_mode == "all_zero": for _ in range(self._num): states.append(generate_state_from_name(c_sys, "z0")) qulacs_density_mat = convert_state_quara_to_qulacs(states[0]) for state in states[1::]: tmp_mat = convert_state_quara_to_qulacs(state) qulacs_density_mat = qulacs_state.tensor_product( tmp_mat, qulacs_density_mat ) return qulacs_density_mat def _generate_initial_state_from_name(self, initial_state_mode) -> DensityMatrix: if self._mode == "qubit": return self._generate_initial_state_from_name_qubit(initial_state_mode) elif self._mode == "qutrit": return self._generate_initial_state_from_name_qutrit(initial_state_mode) def _run_once( self, initial_state_mode: Union[str, None] = None, initial_states: Union[List[State], None] = None, ) -> List[int]: # state preparation if initial_states: qulacs_density_mat = self._generate_initial_state_from_states(initial_states) elif initial_state_mode: qulacs_density_mat = self._generate_initial_state_from_name(initial_state_mode) else: raise ValueError("Define either initial_state_mode or initial_states") raw_result: List[int] = [] for qobject in self._qobjects: qobject['QObject'].update_quantum_state(qulacs_density_mat) # read out value if qobject['Type'] == "MProcess": readout_value = qulacs_density_mat.get_classical_value(0) applied_operator_indices = get_index_tuple_from_kraus_matrices_indices(readout_value, qobject['KrausMatrixIndices']) raw_result.append(applied_operator_indices[0]) return raw_result
[docs] def run( self, num_shots: int, initial_state_mode: Union[str, None] = None, initial_states: Union[List[State], None] = None, ) -> CircuitResult: """Runs the quantum circuit with given initial state and number of shots. Parameters ---------- num_shots: int number of shots to be performed. initial_state_mode optional. Either initial_state_mode or initial_states must be specified. only "all_zero" is available for now. "all_zero" will create a state where all qubits are "z0". initial_state : str optional. Either mprocess or mprocess_name must be specified. A name of a mprocess that will be added to a circuit. Valid mprocess names can be listed by calling get_mprocess_names_type1() and get_mprocess_names_type2(). Returns ------- List[MultinomialDistribution] Number of distributions match to the number of measurement operators in a quantum circuit. """ mprocess_found = False for qobject in self._qobjects[::-1]: if qobject["Type"] == "MProcess": mprocess_found = True if not mprocess_found: raise ValueError("Circuit must contain at least one measurement") result = CircuitResult(list(self)) for _ in range(num_shots): raw_output = self._run_once(initial_state_mode, initial_states) result.append_raw_output(raw_output) result.calc_empi_dist() return result
def __len__(self) -> int: return len(self._qobjects) def __getitem__(self, offset) -> dict: qobj = self._qobjects[offset].copy() del qobj["QObject"] return qobj def __str__(self) -> str: desc = f"Type:\n{self.__class__.__name__}\n\n" desc += f"QObjects:\n[\n" for qobject in self._qobjects: tmp = qobject.copy() del tmp["QObject"] desc += f" {tmp.__str__()},\n" desc += "]\n\n" desc = desc.rstrip("\n") return desc