Source code for qiskit_experiments.library.tomography.basis.local_basis
# This code is part of Qiskit.## (C) Copyright IBM 2022.## 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."""Circuit basis for tomography preparation and measurement circuits"""fromtypingimportSequence,Optional,Tuple,Union,List,Dictimportnumpyasnpfromqiskit.circuitimportQuantumCircuit,Instructionfromqiskit.quantum_info.states.quantum_stateimportQuantumStatefromqiskit.quantum_info.operators.base_operatorimportBaseOperatorfromqiskit.quantum_info.operators.channel.quantum_channelimportQuantumChannelfromqiskit.quantum_infoimportDensityMatrix,Statevector,Operator,SuperOpfromqiskit.exceptionsimportQiskitErrorfrom.base_basisimportPreparationBasis,MeasurementBasisfrom.cache_methodimportcache_method,_method_cache_name# TypingPovm=Union[List[Statevector],List[DensityMatrix],QuantumChannel]States=Union[List[QuantumState],Dict[Tuple[int,...],QuantumState]]
[docs]classLocalPreparationBasis(PreparationBasis):"""Local tensor-product preparation basis. This basis consists of a set of 1-qubit instructions which are used to define a tensor-product basis on N-qubits. """def__init__(self,name:str,instructions:Optional[Sequence[Instruction]]=None,default_states:Optional[States]=None,qubit_states:Optional[Dict[Tuple[int,...],States]]=None,):"""Initialize a fitter preparation basis. Args: name: a name to identify the basis. instructions: list of 1-qubit instructions for preparing states from the :math:`|0\\rangle` state. default_states: Optional, default density matrices prepared by the input instructions. If None these will be determined by ideal simulation of the preparation instructions. qubit_states: Optional, a dict with physical qubit keys and a list of density matrices prepared by the list of basis instructions for a specific qubit. The default states will be used for any qubits not specified in this dict. Raises: QiskitError: If input states or instructions are not valid, or no instructions or states are provided. """ifinstructionsisNoneanddefault_statesisNoneandqubit_statesisNone:raiseQiskitError("LocalPreparationBasis must define at least one of instructions, ""default_states, or qubit_states.")super().__init__(name)# POVM element variablesself._instructions=_format_instructions(instructions)self._default_states=_format_states(default_states,(0,),self._instructions)self._qubit_states=_format_qubit_states(qubit_states)self._custom_defaults=bool(default_states)# Other attributes derived from povms and instructions# that need initializingself._qubits=set()self._size=Noneself._default_dim=Noneself._qubit_dim={}self._hash=None# Initialize attributesself._initialize()def__repr__(self):returnf"<{type(self).__name__}: {self.name}>"def__hash__(self):returnself._hashdef__eq__(self,value):return(super().__eq__(value)andself._size==getattr(value,"_size",None)andself._default_dim==getattr(value,"_default_dim",None)andself._custom_defaults==getattr(value,"_custom_defaults",None)andself._qubits==getattr(value,"_qubits",None)andself._qubit_dim==getattr(value,"_qubit_dim",None)andself._instructions==getattr(value,"_instructions",None))
[docs]defcircuit(self,index:Sequence[int],qubits:Optional[Sequence[int]]=None)->QuantumCircuit:# pylint: disable = unused-argumentifnotself._instructions:raiseNotImplementedError(f"Basis {self.name} does not define circuits so can only be "" used as a fitter basis for analysis.")return_tensor_product_circuit(self._instructions,index,self._name)
[docs]defmatrix(self,index:Sequence[int],qubits:Optional[Sequence[int]]=None):# Convert args to hashable tuplesifqubitsisNone:qubits=tuple(range(len(index)))else:qubits=tuple(qubits)index=tuple(index)try:# Look for custom POVM for specified qubitsstate=self._generate_qubits_state(index,qubits)ifstateisnotNone:returnstate.datamat=np.eye(1)foridx,qubitinzip(index,qubits):qubit_state=self._generate_qubits_state((idx,),(qubit,))mat=np.kron(qubit_state,mat)returnmatexceptTypeErrorasex:# This occurs if basis is constructed with qubit_states# kwarg but no default_states or instructions and is called for# a qubit not in the specified kwargs.raiseValueError(f"Invalid qubits for basis {self.name}")fromex
@cache_method()def_generate_qubits_state(self,index:Tuple[int,...],qubits:Tuple[int,...]):"""LRU cached function for returning POVMS"""num_qubits=len(qubits)# Check for N-qubit statesifqubitsinself._qubit_states:# Get states for specified qubits# TODO: In the future we could add support for different orderings# of qubits by permuting the returned POVMSstates=self._qubit_states[qubits]ifindexinstates:returnstates[index]# Look up custom 0 init state for specified qubits# TODO: Add support for noisy instructionsifnotself._instructions:raiseNotImplementedError(f"Basis {self.name} does not define circuits to construct POVMs from")key0=num_qubits*(0,)ifkey0instates:circuit=_tensor_product_circuit(self._instructions,index,self._name)return_generate_state(circuit,states[key0])# No match, so if 1-qubit use default, otherwise return Noneifnum_qubits==1andself._default_states:returnself._default_states[index]returnNonedef_initialize(self):"""Initialize dimension and num outcomes"""ifself._instructions:self._size=len(self._instructions)# Format default POVMsifself._default_states:default_state=next(iter(self._default_states.values()))self._default_dim=np.prod(default_state.dims())ifself._sizeisNone:self._size=len(self._default_states)eliflen(self._default_states)!=self._size:raiseQiskitError("Number of instructions and number of states must be equal.")# Format qubit statesforqubits,statesinself._qubit_states.items():state=next(iter(states.values()))num_qubits=len(qubits)dims=state.dims()ifnum_qubits==1:qubit_dim=(np.prod(dims),)eliflen(dims)==num_qubits:qubit_dim=tuple(dims)else:# Assume all subsystems have the same dimension if the provided# state dimension don't match number of qubitsave_dim=np.prod(dims)**(1/num_qubits)ifint(ave_dim)!=ave_dim:raiseQiskitError("Cannot infer unequal subsystem dimensions from input states")qubit_dim=num_qubits*(int(ave_dim),)self._qubit_dim[qubits]=qubit_dimself._qubits.update(qubits)# Pseudo hash value to make basis hashable for LRU cached functionsself._hash=hash((type(self),self._name,self._size,self._default_dim,self._custom_defaults,tuple(self._qubits),tuple(sorted(self._qubit_dim.items())),tuple(type(i)foriinself._instructions),))def__json_encode__(self):value={"name":self._name,"instructions":list(self._instructions)ifself._instructionselseNone,}ifself._custom_defaults:value["default_states"]=self._default_statesifself._qubit_states:value["qubit_states"]=self._qubit_statesreturnvaluedef__getstate__(self):# override get state to skip class cache when picklingstate=self.__dict__.copy()state.pop(_method_cache_name(self),None)returnstate
[docs]classLocalMeasurementBasis(MeasurementBasis):"""Local tensor-product measurement basis. This basis consists of a set of 1-qubit instructions which are used to define a tensor-product basis on N-qubits to rotate a desired multi-qubit measurement basis to the Z-basis measurement. """def__init__(self,name:str,instructions:Optional[Sequence[Instruction]]=None,default_povms:Optional[Sequence[Povm]]=None,qubit_povms:Optional[Dict[Tuple[int,...],Sequence[Povm]]]=None,):"""Initialize a fitter preparation basis. Args: name: a name to identity the basis. instructions: list of instructions for rotating a desired measurement basis to the standard :math:`Z^{\\otimes n}` computational basis measurement. default_povms: Optional, list if positive operators valued measures (POVM) for of the measurement basis instructions. A POVM can be input as a list of effects (Statevector or DensityMatrix) for each possible measurement outcome of that basis, or as a single QuantumChannel. For the channel case the effects will be calculated by evolving the computation basis states by the adjoint of the channel. If None the input instructions will be used as the POVM channel. qubit_povms: Optional, a dict with physical qubit keys and a list of POVMs corresponding to each basis measurement instruction for the specific qubit. The default POVMs will be used for any qubits not specified in this dict. Raises: QiskitError: If the input instructions or POVMs are not valid, or if no instructions or POVMs are provided. """ifinstructionsisNoneanddefault_povmsisNoneandqubit_povmsisNone:raiseQiskitError("LocalMeasurementBasis must define at least one of instructions, ""default_povms, or qubit_povms.")super().__init__(name)# POVM element variablesself._instructions=_format_instructions(instructions)self._default_povms=_format_default_povms(default_povms,self._instructions)self._qubit_povms=_format_qubit_povms(qubit_povms)self._custom_defaults=bool(default_povms)# Other attributes derived from povms and instructions# that need initializingself._qubits=set()self._size=Noneself._default_num_outcomes=Noneself._default_dim=Noneself._qubit_num_outcomes={}self._qubit_dim={}self._hash=None# Initialize attributesself._initialize()def__repr__(self):returnf"<{type(self).__name__}: {self.name}>"def__hash__(self):returnself._hashdef__eq__(self,value):return(super().__eq__(value)andself._size==getattr(value,"_size",None)andself._default_dim==getattr(value,"_default_dim",None)andself._default_num_outcomes==getattr(value,"_default_num_outcomes",None)andself._custom_defaults==getattr(value,"_custom_defaults",None)andself._qubit_dim==getattr(value,"_qubit_dim",None)andself._qubit_num_outcomes==getattr(value,"_qubit_num_outcomes",None)andself._qubits==getattr(value,"_qubits",None)andself._instructions==getattr(value,"_instructions",None))
[docs]defcircuit(self,index:Sequence[int],qubits:Optional[Sequence[int]]=None):# pylint: disable = unused-argumentifnotself._instructions:raiseNotImplementedError(f"Basis {self.name} does not define circuits so can only be "" used as a fitter basis for analysis.")circuit=_tensor_product_circuit(self._instructions,index,self._name)circuit.measure_all()returncircuit
[docs]defmatrix(self,index:Sequence[int],outcome:int,qubits:Optional[Sequence[int]]=None):# Convert args to hashable tuplesifqubitsisNone:qubits=tuple(range(len(index)))else:qubits=tuple(qubits)index=tuple(index)try:# Look for custom POVM for specified qubitsqubit_povm=self._generate_qubits_povm(index,qubits)ifqubit_povm:returnqubit_povm[outcome].data# Otherwise construct tensor product POVMoutcome_index=self._outcome_indices(outcome,qubits)mat=np.eye(1)foridx,odx,qubitinzip(index,outcome_index,qubits):povm=self._generate_qubits_povm((idx,),(qubit,))mat=np.kron(povm[odx],mat)returnmatexceptTypeErrorasex:# This occurs if basis is constructed with qubit_states# kwarg but no default_states or instructions and is called for# a qubit not in the specified kwargs.raiseValueError(f"Invalid qubits for basis {self.name}")fromex
def_initialize(self):"""Initialize dimension and num outcomes"""ifself._instructions:self._size=len(self._instructions)# Format default POVMsifself._default_povms:default_povm=next(iter(self._default_povms.values()))self._default_num_outcomes=len(default_povm)self._default_dim=np.prod(default_povm[0].dims())ifself._sizeisNone:self._size=len(self._default_povms)eliflen(self._default_povms)!=self._size:raiseQiskitError("Number of instructions and number of states must be equal.")ifany(len(povm)!=self._default_num_outcomesforpovminself._default_povms.values()):raiseQiskitError("LocalMeasurementBasis default POVM elements must all have ""the same number of outcomes.")# Format qubit POVMSforqubits,povmsinself._qubit_povms.items():povm=next(iter(povms.values()))num_povms=len(povm)ifany(len(povm)!=num_povmsforpovminpovms.values()):raiseQiskitError("LocalMeasurementBasis POVM elements must all have the ""same number of outcomes.")num_qubits=len(qubits)dims=povm[0].dims()dim=np.prod(dims)ifnum_qubits==1:qubit_dim=(dim,)num_outcomes=(num_povms,)eliflen(dims)==num_qubits:qubit_dim=tuple(dims)ifdim!=num_povms:raiseQiskitError("POVMs dimensions don't match number of outcomes")num_outcomes=qubit_dimelse:# Assume all subsystems have the same dimension if the provided# operator dimension don't match number of qubitsave_dim=np.prod(dims)**(1/num_qubits)ifint(ave_dim)!=ave_dim:raiseQiskitError("Cannot infer unequal subsystem dimensions from input POVMs")qubit_dim=num_qubits*(int(ave_dim),)ave_num_outcomes=num_povms**(1/num_qubits)ifint(ave_num_outcomes)!=ave_num_outcomes:raiseQiskitError("Cannot infer unequal subsystem num_outcome from input POVMs")num_outcomes=num_qubits*(int(ave_dim),)self._qubit_num_outcomes[qubits]=num_outcomesself._qubit_dim[qubits]=qubit_dimself._qubits.update(qubits)# Pseudo hash value to make basis hashable for LRU cached functionsself._hash=hash((type(self),self._name,self._size,self._default_dim,self._default_num_outcomes,self._custom_defaults,tuple(self._qubits),tuple(sorted(self._qubit_dim.items())),tuple(sorted(self._qubit_num_outcomes.items())),tuple(type(i)foriinself._instructions),))@cache_method()def_outcome_indices(self,outcome:int,qubits:Tuple[int,...])->Tuple[int,...]:"""Convert an outcome integer to a tuple of single-qubit outcomes"""num_outcomes=np.prod(self._qubit_num_outcomes.get(qubits[:1],self._default_num_outcomes))try:value=(outcome%num_outcomes,)iflen(qubits)==1:returnvaluereturnvalue+self._outcome_indices(outcome//num_outcomes,qubits[1:])exceptTypeErrorasex:raiseValueError("Invalid qubits for basis")fromex@cache_method()def_generate_qubits_povm(self,index:Tuple[int,...],qubits:Tuple[int,...]):"""LRU cached function for returning POVMS"""num_qubits=len(qubits)ifqubitsinself._qubit_povms:# Get POVMS for specified qubits# TODO: In the future we could add support for different orderings# of qubits by permuting the returned POVMSpovms=self._qubit_povms[qubits]ifindexinpovms:returnpovms[index]# Look up custom Z-default POVM for specified qubits# TODO: Add support for noisy instructionsifnotself._instructions:raiseNotImplementedError(f"Basis {self.name} does not define circuits to construct POVMs from")key0=num_qubits*(0,)ifkey0inpovms:circuit=_tensor_product_circuit(self._instructions,index,self._name)return_generate_povm(circuit,povms[key0])# No match, so if 1-qubit use default, otherwise return Noneifnum_qubits==1andself._default_povms:returnself._default_povms[index[0]]returnNonedef__json_encode__(self):value={"name":self._name,"instructions":self._instructionsifself._instructionselseNone,}ifself._custom_defaults:value["default_povms"]=self._default_povmsifself._qubit_povms:value["qubit_povms"]=self._qubit_povmsreturnvaluedef__getstate__(self):# override get state to skip class cache when picklingstate=self.__dict__.copy()state.pop(_method_cache_name(self),None)returnstate
def_tensor_product_circuit(instructions:Sequence[Instruction],index:Sequence[int],name:str="",)->QuantumCircuit:"""Return tensor product of 1-qubit basis instructions"""size=len(instructions)circuit=QuantumCircuit(len(index),name=f"{name}{list(index)}")fori,eltinenumerate(index):ifelt>=size:raiseQiskitError("Invalid basis element index")circuit.append(instructions[elt],[i])returncircuitdef_format_instructions(instructions:Sequence[any])->List[Instruction]:"""Parse multiple input formats for list of instructions"""ret=[]ifinstructionsisNone:returnretforinstininstructions:# Convert to instructions if object is not an instruction# This allows converting raw unitary matrices and other operator# types like Pauli or Clifford into instructions.ifnotisinstance(inst,Instruction):ifhasattr(inst,"to_instruction"):inst=inst.to_instruction()else:inst=Operator(inst).to_instruction()# Validate that instructions are single qubitifinst.num_qubits!=1:raiseQiskitError(f"Input instruction {inst.name} is not a 1-qubit instruction.")ret.append(inst)returnretdef_generate_povm(value:Union[List[DensityMatrix],Instruction,Operator,SuperOp],default_z:Optional[List[DensityMatrix]]=None,dims:Optional[Tuple[int,...]]=None,)->List[DensityMatrix]:"""Format a POVM into list of density matrix effects"""# If already a list convert to DensityMatrix objectsifisinstance(value,(list,tuple)):return[DensityMatrix(i,dims=dims)foriinvalue]# Otherwise convert from operator/channel to POVM effectstry:chan=Operator(value)exceptQiskitError:chan=SuperOp(value)adjoint=chan.adjoint()ifdimsisNone:dims=adjoint.input_dims()ifdefault_zisnotNone:z_states=[DensityMatrix(i,dims)foriindefault_z]else:z_states=[DensityMatrix.from_int(i,dims)foriinrange(np.prod(dims))]return[state.evolve(adjoint)forstateinz_states]def_format_default_povms(default_povms:any,instructions:Optional[Sequence[Instruction]]=None)->Dict[Tuple[int,...],List[DensityMatrix]]:"Format default POVM data"# Parse data into a dict# Legacy data handlingifisinstance(default_povms,(list,tuple,np.ndarray)):povms=dict(enumerate(default_povms))elifisinstance(default_povms,dict):povms={int(key):valforkey,valindefault_povms.items()}elifnotdefault_povms:povms={}# Add instructions to POVM dict if not specified in dataifinstructionsandlen(povms)<len(instructions):fori,instinenumerate(instructions):ifinotinpovms:povms[i]=inst# Look for default POVMs for Zif0inpovmsandisinstance(povms[0],(list,tuple)):default_z=povms[0]else:default_z=None# Format remaining POVM values and update attributereturn{key:_generate_povm(val,default_z)forkey,valinpovms.items()}def_format_qubit_povms(qubit_povms:any,)->Dict[Tuple[int,...],Dict[Tuple[int,...],List[DensityMatrix]]]:"""Format qubit POVMs dict"""povms={}ifnotqubit_povms:returnpovms# Format POVM keys to be a tuple of (qubits, basis)forqubits,povminqubit_povms.items():ifisinstance(qubits,int):qubits=(qubits,)# Convert value to dict if not alreadyifnotisinstance(povm,dict):formatted_povm={(i,):valfori,valinenumerate(povm)}else:# Format dict keysformatted_povm={}forindex,valueinpovm.items():ifisinstance(index,int):index=(index,)formatted_povm[tuple(index)]=value# Add qubit POVM dict to povmspovms[qubits]=formatted_povm# Format POVM valuesforqubits,povminpovms.items():# Convert any Z-povm value if presentkey0=len(qubits)*(0,)ifkey0inpovmandisinstance(povm[key0],(list,tuple)):default_z=povm[key0]else:default_z=None# Convert any values from instructions/channels# By applying to Z-povmforkey,valueinpovm.items():povm[key]=_generate_povm(value,default_z)returnpovmsdef_generate_state(value:Union[Statevector,DensityMatrix,Instruction,Operator,SuperOp],init_state:Optional[QuantumState]=None,dims:Optional[Tuple[int,...]]=None,)->DensityMatrix:"""Format a state into list of DensityMatrix"""# If already a quantum state convert to a density matrixifisinstance(value,(QuantumState,np.ndarray,list)):returnDensityMatrix(value,dims=dims)# Otherwise convert from operator/channel to POVM effectstry:chan=Operator(value)exceptQiskitError:chan=SuperOp(value)ifdimsisNone:dims=chan.input_dims()# Default |0> state density matrixifinit_stateisNone:init_state=DensityMatrix.from_int(0,dims)elifnotisinstance(init_state,DensityMatrix):init_state=DensityMatrix(init_state,dims)returninit_state.evolve(chan)def_format_states(states:Optional[Union[List[any],Dict[Tuple[int,...],any]]],init_key:Tuple[int,...]=(0,),instructions:Optional[Sequence[Instruction]]=None,)->Dict[Tuple[int,...],DensityMatrix]:"Format default state data"# Parse data into dict to include legacy handlingstates=_format_data_dict(states,instructions)# Look for default stateinit_val=states.get(init_key,None)ifisinstance(init_val,(QuantumState,np.ndarray,list)):init_state=DensityMatrix(init_val)else:init_state=None# Format remaining states and update attributereturn{key:_generate_state(val,init_state)forkey,valinstates.items()}def_format_qubit_states(qubit_states:any,)->Dict[Tuple[int,...],Dict[Tuple[int,...],DensityMatrix]]:"""Format qubit POVMs dict"""ifnotqubit_states:return{}# Format POVM keys to be a tuple of (qubits, basis)formatted_states={}forqubits,statesinqubit_states.items():ifisinstance(qubits,int):qubits=(qubits,)else:qubits=tuple(qubits)init_key=len(qubits)*(0,)formatted_states[qubits]=_format_states(states,init_key)returnformatted_statesdef_format_data_dict(data:Optional[any],instructions:Optional[Sequence[Union[Instruction,BaseOperator]]]=None)->Dict[Tuple[int,...],any]:"Format default state data"# Parse data into dict to include legacy handlingifisinstance(data,(list,tuple,np.ndarray)):iter_data=enumerate(data)elifisinstance(data,dict):iter_data=data.items()elifnotdata:iter_data={}.items()else:iter_data=data# Format arg to tuple keysdict_data={((key,)ifisinstance(key,int)elsetuple(key)):valforkey,valiniter_data}# Add instructions for unspecified dataifinstructionsandlen(dict_data)<len(instructions):fori,instinenumerate(instructions):if(i,)notindict_data:dict_data[(i,)]=instreturndict_data