Source code for qiskit_experiments.library.tomography.tomography_experiment
# This code is part of Qiskit.## (C) Copyright IBM 2021.## This code is licensed under the Apache License, Version 2.0. You may# obtain a copy of this license in the LICENSE.txt file in the root directory# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.## Any modifications or derivative works of this code must retain this# copyright notice, and modified files need to carry a notice indicating# that they have been altered from the originals."""Quantum Tomography experiment"""fromtypingimportUnion,Optional,Iterable,List,Tuple,Sequencefromitertoolsimportproductfromqiskit.circuitimportQuantumCircuit,Instruction,ClassicalRegister,Clbitfromqiskit.circuit.libraryimportPermutationGatefromqiskit.providers.backendimportBackendfromqiskit.quantum_info.operators.base_operatorimportBaseOperatorfromqiskit_experiments.exceptionsimportQiskitErrorfromqiskit_experiments.frameworkimportBaseExperiment,BaseAnalysis,Optionsfrom.basisimportPreparationBasis,MeasurementBasisfrom.tomography_analysisimportTomographyAnalysis
[docs]classTomographyExperiment(BaseExperiment):"""Base experiment for quantum state and process tomography. # section: analysis_ref :class:`TomographyAnalysis` """@classmethoddef_default_experiment_options(cls)->Options:"""Default experiment options. Experiment Options: basis_indices (Iterable[Tuple[List[int], List[int]]]): The basis elements to be measured. If None All basis elements will be measured. """options=super()._default_experiment_options()options.basis_indices=Nonereturnoptionsdef__init__(self,circuit:Union[QuantumCircuit,Instruction,BaseOperator],backend:Optional[Backend]=None,physical_qubits:Optional[Sequence[int]]=None,measurement_basis:Optional[MeasurementBasis]=None,measurement_indices:Optional[Sequence[int]]=None,preparation_basis:Optional[PreparationBasis]=None,preparation_indices:Optional[Sequence[int]]=None,conditional_circuit_clbits:Union[bool,Sequence[int],Sequence[Clbit]]=False,basis_indices:Optional[Iterable[Tuple[List[int],List[int]]]]=None,analysis:Union[BaseAnalysis,None,str]="default",):"""Initialize a tomography experiment. Args: circuit: the quantum process circuit. If not a quantum circuit it must be a class that can be appended to a quantum circuit. backend: The backend to run the experiment on. physical_qubits: Optional, the physical qubits for the initial state circuit. If None this will be qubits [0, N) for an N-qubit circuit. measurement_basis: Tomography basis for measurements. If set to None no tomography measurements will be performed. measurement_indices: Optional, the `physical_qubits` indices to be measured as specified by the `measurement_basis`. If None all circuit physical qubits will be measured. preparation_basis: Tomography basis for measurements. If set to None no tomography preparations will be performed. preparation_indices: Optional, the `physical_qubits` indices to be prepared as specified by the `preparation_basis`. If None all circuit physical qubits will be prepared. basis_indices: Optional, the basis elements to be measured. If None All basis elements will be measured. conditional_circuit_clbits: Specify any clbits in the input circuit to treat as conditioning bits for conditional tomography. If set to True all circuit clbits will be treated as conditional. If False all circuit clbits will be marginalized over (Default: False). analysis: Optional, a custom analysis instance to use. If ``"default"`` :class:`~.TomographyAnalysis` will be used. If None no analysis instance will be set. Raises: QiskitError: If input params are invalid. """# Initialize BaseExperimentifphysical_qubitsisNone:physical_qubits=tuple(range(circuit.num_qubits))ifanalysis=="default":analysis=TomographyAnalysis()super().__init__(physical_qubits,analysis=analysis,backend=backend)# Get the target tomography circuitifisinstance(circuit,QuantumCircuit):target_circuit=circuitelse:# Convert input to a circuitnum_qubits=circuit.num_qubitstarget_circuit=QuantumCircuit(num_qubits)target_circuit.append(circuit,range(num_qubits))self._circuit=target_circuitself._cond_clbits=Noneifconditional_circuit_clbitsisTrue:conditional_circuit_clbits=self._circuit.clbitsifconditional_circuit_clbits:cond_clbits=[]foriinconditional_circuit_clbits:ifisinstance(i,Clbit):cond_clbits.append(self._circuit.find_bit(i).index)elifi<self._circuit.num_clbits:cond_clbits.append(i)else:raiseQiskitError(f"Circuit {self._circuit.name} does not contain conditional clbit {i}")self._cond_clbits=cond_clbits# Measurement basis and qubitsself._meas_circ_basis=measurement_basisifmeasurement_indices:# Convert logical qubits to physical qubitsself._meas_indices=tuple(measurement_indices)self._meas_physical_qubits=tuple(self.physical_qubits[i]foriinself._meas_indices)forqubitinself._meas_indices:ifqubitnotinrange(self.num_qubits):raiseQiskitError(f"measurement qubit ({qubit}) is outside the range"f" of circuit qubits [0, {self.num_qubits}).")elifmeasurement_basis:self._meas_indices=tuple(range(self.num_qubits))self._meas_physical_qubits=self.physical_qubitselse:self._meas_indices=tuple()self._meas_physical_qubits=tuple()# Preparation basis and qubitsself._prep_circ_basis=preparation_basisifpreparation_indices:self._prep_indices=tuple(preparation_indices)self._prep_physical_qubits=tuple(self.physical_qubits[i]foriinself._prep_indices)forqubitinself._prep_indices:ifqubitnotinrange(self.num_qubits):raiseQiskitError(f"preparation qubit ({qubit}) is outside the range"f" of circuit qubits [0, {self.num_qubits}).")elifpreparation_basis:self._prep_indices=tuple(range(self.num_qubits))self._prep_physical_qubits=self.physical_qubitselse:self._prep_indices=tuple()self._prep_physical_qubits=tuple()# Configure experiment optionsifbasis_indices:self.set_experiment_options(basis_indices=basis_indices)# Configure analysis basis optionsifisinstance(self.analysis,TomographyAnalysis):analysis_options={}ifmeasurement_basis:analysis_options["measurement_basis"]=measurement_basisanalysis_options["measurement_qubits"]=self._meas_physical_qubitsifpreparation_basis:analysis_options["preparation_basis"]=preparation_basisanalysis_options["preparation_qubits"]=self._prep_physical_qubitsifconditional_circuit_clbits:analysis_options["conditional_circuit_clbits"]=self._cond_clbitsself.analysis.set_options(**analysis_options)
[docs]defcircuits(self):circ_qubits=self._circuit.qubitscirc_clbits=self._circuit.clbitsmeas_creg=ClassicalRegister((len(self._meas_indices)),name="c_tomo")template=QuantumCircuit(*self._circuit.qregs,*self._circuit.cregs,meas_creg,name=f"{self._type}")ifself._circuit.metadata:template.metadata=self._circuit.metadata.copy()else:template.metadata={}meas_clbits=[template.find_bit(i).indexforiinmeas_creg]# Build circuitscircuits=[]forprep_element,meas_elementinself._basis_indices():name=template.namemetadata={"clbits":meas_clbits,"cond_clbits":self._cond_clbits}ifmeas_element:name+=f"_{meas_element}"metadata["m_idx"]=list(meas_element)ifprep_element:name+=f"_{prep_element}"metadata["p_idx"]=list(prep_element)circ=template.copy(name=name)ifprep_element:# Add tomography preparationprep_circ=self._prep_circ_basis.circuit(prep_element,self._prep_physical_qubits)circ.compose(prep_circ,self._prep_indices,inplace=True)circ.barrier(*self._prep_indices)# Add target circuit# Have to use compose since circuit.to_instruction has a bug# when circuit contains classical registers and conditionalscirc.compose(self._circuit,circ_qubits,circ_clbits,inplace=True)# Add tomography measurementifmeas_element:meas_circ=self._meas_circ_basis.circuit(meas_element,self._meas_physical_qubits)circ.barrier(*self._meas_indices)circ.compose(meas_circ,self._meas_indices,meas_clbits,inplace=True)# Add metadatacirc.metadata.update(**metadata)circuits.append(circ)returncircuits
def_metadata(self):metadata=super()._metadata()ifself._meas_physical_qubits:metadata["m_qubits"]=list(self._meas_physical_qubits)ifself._prep_physical_qubits:metadata["p_qubits"]=list(self._prep_physical_qubits)returnmetadatadef_basis_indices(self):"""Return list of basis element indices"""basis_indices=self.experiment_options.basis_indicesifbasis_indicesisnotNone:returnbasis_indicesifself._meas_circ_basis:meas_shape=self._meas_circ_basis.index_shape(self._meas_physical_qubits)ranges=[range(i)foriinmeas_shape]meas_elements=product(*ranges)else:meas_elements=[None]ifself._prep_circ_basis:prep_shape=self._prep_circ_basis.index_shape(self._prep_physical_qubits)prep_elements=product(*[range(i)foriinprep_shape])else:prep_elements=[None]returnproduct(prep_elements,meas_elements)def_permute_circuit(self)->QuantumCircuit:"""Permute circuit qubits. This permutes the circuit so that the specified preparation and measurement qubits correspond to input and output qubits [0, ..., N-1] and [0, ..., M-1] respectively for the returned circuit. """default_range=tuple(range(self.num_qubits))permute_meas=self._meas_indicesandself._meas_indices!=default_rangepermute_prep=self._prep_indicesandself._prep_indices!=default_rangeifnotpermute_measandnotpermute_prep:returnself._circuittotal_qubits=self._circuit.num_qubitstotal_clbits=self._circuit.num_clbitsiftotal_clbits:perm_circ=QuantumCircuit(total_qubits,total_clbits)else:perm_circ=QuantumCircuit(total_qubits)# Apply permutation to put prep qubits as [0, ..., M-1]ifself._prep_indices:prep_qargs=list(self._prep_indices)iflen(self._prep_indices)!=total_qubits:prep_qargs+=[iforiinrange(total_qubits)ifinotinself._prep_indices]perm_circ.append(PermutationGate(prep_qargs).inverse(),range(total_qubits))# Apply original circuitiftotal_clbits:perm_circ=perm_circ.compose(self._circuit,range(total_qubits),range(total_clbits))else:perm_circ=perm_circ.compose(self._circuit,range(total_qubits))# Apply permutation to put meas qubits as [0, ..., M-1]ifself._meas_indices:meas_qargs=list(self._meas_indices)iflen(self._meas_indices)!=total_qubits:meas_qargs+=[iforiinrange(total_qubits)ifinotinself._meas_indices]perm_circ.append(PermutationGate(meas_qargs),range(total_qubits))returnperm_circ