Source code for qiskit_experiments.library.randomized_benchmarking.clifford_utils
# This code is part of Qiskit.## (C) Copyright IBM 2021, 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."""Utilities for using the Clifford group in randomized benchmarking."""importitertoolsimportosfromfunctoolsimportlru_cachefromnumbersimportIntegralfromtypingimportOptional,Union,Tuple,Sequence,Iterableimportnumpyasnpfromqiskit.circuitimportCircuitInstruction,Qubitfromqiskit.circuitimportGate,Instructionfromqiskit.circuitimportQuantumCircuit,QuantumRegisterfromqiskit.circuit.libraryimportSdgGate,HGate,SGate,XGate,YGate,ZGatefromqiskit.compilerimporttranspilefromqiskit.exceptionsimportQiskitErrorfromqiskit.quantum_infoimportCliffordfromqiskit.transpilerimportCouplingMap,PassManagerfromqiskit.transpiler.passes.synthesis.high_level_synthesisimportHLSConfig,HighLevelSynthesisDEFAULT_SYNTHESIS_METHOD="rb_default"_DATA_FOLDER=os.path.join(os.path.dirname(__file__),"data")_CLIFFORD_COMPOSE_1Q=np.load(f"{_DATA_FOLDER}/clifford_compose_1q.npz")["table"]_CLIFFORD_INVERSE_1Q=np.load(f"{_DATA_FOLDER}/clifford_inverse_1q.npz")["table"]_CLIFFORD_INVERSE_2Q=np.load(f"{_DATA_FOLDER}/clifford_inverse_2q.npz")["table"]_clifford_compose_2q_data=np.load(f"{_DATA_FOLDER}/clifford_compose_2q_dense_selected.npz")_CLIFFORD_COMPOSE_2Q_DENSE=_clifford_compose_2q_data["table"]# valid indices for the columns of the _CLIFFORD_COMPOSE_2Q_DENSE table_valid_sparse_indices=_clifford_compose_2q_data["valid_sparse_indices"]# map a clifford number to the index of _CLIFFORD_COMPOSE_2Q_DENSE_clifford_num_to_dense_index={idx:iiforii,idxinenumerate(_valid_sparse_indices)}_CLIFFORD_TENSOR_1Q=np.load(f"{_DATA_FOLDER}/clifford_tensor_1q.npz")["table"]# Transpilation utilitiesdef_transpile_clifford_circuit(circuit:QuantumCircuit,physical_qubits:Sequence[int])->QuantumCircuit:# Simplified transpile that only decomposes Clifford circuits and creates the layout.return_apply_qubit_layout(_decompose_clifford_ops(circuit),physical_qubits=physical_qubits)def_decompose_clifford_ops(circuit:QuantumCircuit)->QuantumCircuit:# Simplified QuantumCircuit.decompose, which decomposes only Clifford opsres=circuit.copy_empty_like()ifhasattr(circuit,"_parameter_table"):res._parameter_table=circuit._parameter_tableforinstincircuit:ifinst.operation.name.startswith("Clifford"):# Decomposerule=inst.operation.definition.dataiflen(rule)==1andlen(inst.qubits)==len(rule[0].qubits):ifinst.operation.definition.global_phase:res.global_phase+=inst.operation.definition.global_phaseres._data.append(CircuitInstruction(operation=rule[0].operation,qubits=inst.qubits,clbits=inst.clbits,))else:_circuit_compose(res,inst.operation.definition,qubits=inst.qubits)else:# Keep the original instructionres._data.append(inst)returnresdef_apply_qubit_layout(circuit:QuantumCircuit,physical_qubits:Sequence[int])->QuantumCircuit:# Mapping qubits in circuit to physical qubits (layout)res=QuantumCircuit(1+max(physical_qubits),name=circuit.name,metadata=circuit.metadata)res.add_bits(circuit.clbits)forregincircuit.cregs:res.add_register(reg)_circuit_compose(res,circuit,qubits=physical_qubits)ifhasattr(circuit,"_parameter_table"):res._parameter_table=circuit._parameter_tablereturnresdef_circuit_compose(self:QuantumCircuit,other:QuantumCircuit,qubits:Sequence[Union[Qubit,int]])->QuantumCircuit:# Simplified QuantumCircuit.compose with clbits=None, front=False, inplace=True, wrap=False# without any validation, parameter_table/calibrations updates and copy of operations# The input circuit `self` is changed inplace.qubit_map={other.qubits[i]:(self.qubits[q]ifisinstance(q,int)elseq)fori,qinenumerate(qubits)}forinstrinother:self._data.append(CircuitInstruction(operation=instr.operation,qubits=[qubit_map[q]forqininstr.qubits],clbits=instr.clbits,),)self.global_phase+=other.global_phasereturnselfdef_synthesize_clifford(clifford:Clifford,basis_gates:Optional[Tuple[str]],coupling_tuple:Optional[Tuple[Tuple[int,int]]]=None,synthesis_method:str=DEFAULT_SYNTHESIS_METHOD,)->QuantumCircuit:"""Synthesize a circuit of a Clifford element. The resulting circuit contains only ``basis_gates`` and it complies with ``coupling_tuple``. Args: clifford: Clifford element to be converted basis_gates: basis gates to use in the conversion coupling_tuple: coupling map to use in the conversion in the form of tuple of edges synthesis_method: conversion algorithm name Returns: Synthesized circuit """qc=QuantumCircuit(clifford.num_qubits,name=str(clifford))qc.append(clifford,qc.qubits)return_synthesize_clifford_circuit(qc,basis_gates=basis_gates,coupling_tuple=coupling_tuple,synthesis_method=synthesis_method,)def_synthesize_clifford_circuit(circuit:QuantumCircuit,basis_gates:Optional[Tuple[str]],coupling_tuple:Optional[Tuple[Tuple[int,int]]]=None,synthesis_method:str=DEFAULT_SYNTHESIS_METHOD,)->QuantumCircuit:"""Convert a Clifford circuit into one composed of ``basis_gates`` with satisfying ``coupling_tuple`` using the specified synthesis method. Args: circuit: Clifford circuit to be converted basis_gates: basis gates to use in the conversion coupling_tuple: coupling map to use in the conversion in the form of tuple of edges synthesis_method: name of Clifford synthesis algorithm to use Returns: Synthesized circuit """ifbasis_gates:basis_gates=list(basis_gates)coupling_map=CouplingMap(coupling_tuple)ifcoupling_tupleelseNone# special handling for 1q or 2q case for speedifcircuit.num_qubits<=2:ifsynthesis_method==DEFAULT_SYNTHESIS_METHOD:returntranspile(circuit,basis_gates=basis_gates,coupling_map=coupling_map,optimization_level=1,)else:# Provided custom synthesis method, re-synthesize Clifford circuit# convert the circuit back to a Clifford object and then call the synthesis pluginnew_circuit=QuantumCircuit(circuit.num_qubits,name=circuit.name)new_circuit.append(Clifford(circuit),new_circuit.qubits)circuit=new_circuit# for 3q+ or custom synthesis method, synthesizes clifford circuithls_config=HLSConfig(clifford=[(synthesis_method,{"basis_gates":basis_gates})])pm=PassManager([HighLevelSynthesis(hls_config=hls_config,coupling_map=coupling_map)])circuit=pm.run(circuit)returncircuit@lru_cache(maxsize=256)def_clifford_1q_int_to_instruction(num:Integral,basis_gates:Optional[Tuple[str]],synthesis_method:str=DEFAULT_SYNTHESIS_METHOD,)->Instruction:returnCliffordUtils.clifford_1_qubit_circuit(num,basis_gates=basis_gates,synthesis_method=synthesis_method).to_instruction()@lru_cache(maxsize=11520)def_clifford_2q_int_to_instruction(num:Integral,basis_gates:Optional[Tuple[str]],coupling_tuple:Optional[Tuple[Tuple[int,int]]],synthesis_method:str=DEFAULT_SYNTHESIS_METHOD,)->Instruction:returnCliffordUtils.clifford_2_qubit_circuit(num,basis_gates=basis_gates,coupling_tuple=coupling_tuple,synthesis_method=synthesis_method,).to_instruction()def_hash_cliff(cliff):returncliff.tableau.tobytes(),cliff.tableau.shapedef_dehash_cliff(cliff_hash):tableau=np.frombuffer(cliff_hash[0],dtype=bool).reshape(cliff_hash[1])returnClifford(tableau)def_clifford_to_instruction(clifford:Clifford,basis_gates:Optional[Tuple[str]],coupling_tuple:Optional[Tuple[Tuple[int,int]]],synthesis_method:str=DEFAULT_SYNTHESIS_METHOD,)->Instruction:return_cached_clifford_to_instruction(_hash_cliff(clifford),basis_gates=basis_gates,coupling_tuple=coupling_tuple,synthesis_method=synthesis_method,)@lru_cache(maxsize=256)def_cached_clifford_to_instruction(cliff_hash:Tuple[str,Tuple[int,int]],basis_gates:Optional[Tuple[str]],coupling_tuple:Optional[Tuple[Tuple[int,int]]],synthesis_method:str=DEFAULT_SYNTHESIS_METHOD,)->Instruction:return_synthesize_clifford(_dehash_cliff(cliff_hash),basis_gates=basis_gates,coupling_tuple=coupling_tuple,synthesis_method=synthesis_method,).to_instruction()# The classes VGate and WGate are not actually used in the code - we leave them here to give# a better understanding of the composition of the layers for 2-qubit Cliffords.classVGate(Gate):"""V Gate used in Clifford synthesis."""def__init__(self):"""Create new V Gate."""super().__init__("v",1,[])def_define(self):"""V Gate definition."""q=QuantumRegister(1,"q")qc=QuantumCircuit(q)qc.data=[(SdgGate(),[q[0]],[]),(HGate(),[q[0]],[])]self.definition=qcclassWGate(Gate):"""W Gate used in Clifford synthesis."""def__init__(self):"""Create new W Gate."""super().__init__("w",1,[])def_define(self):"""W Gate definition."""q=QuantumRegister(1,"q")qc=QuantumCircuit(q)qc.data=[(HGate(),[q[0]],[]),(SGate(),[q[0]],[])]self.definition=qc
[docs]classCliffordUtils:"""Utilities for generating one- and two-qubit Clifford circuits and elements."""NUM_CLIFFORD_1_QUBIT=24NUM_CLIFFORD_2_QUBIT=11520CLIFFORD_1_QUBIT_SIG=(2,3,4)CLIFFORD_2_QUBIT_SIGS=[# TODO: deprecate(2,2,3,3,4,4),(2,2,3,3,3,3,4,4),(2,2,3,3,3,3,4,4),(2,2,3,3,4,4),]
[docs]@classmethod@lru_cache(maxsize=24)defclifford_1_qubit(cls,num):"""Return the 1-qubit clifford element corresponding to `num` where `num` is between 0 and 23. """returnClifford(cls.clifford_1_qubit_circuit(num),validate=False)
[docs]@classmethod@lru_cache(maxsize=11520)defclifford_2_qubit(cls,num):"""Return the 2-qubit clifford element corresponding to ``num``, where ``num`` is between 0 and 11519. """returnClifford(cls.clifford_2_qubit_circuit(num),validate=False)
[docs]@classmethod@lru_cache(maxsize=24)defclifford_1_qubit_circuit(cls,num,basis_gates:Optional[Tuple[str,...]]=None,synthesis_method:str=DEFAULT_SYNTHESIS_METHOD,):"""Return the 1-qubit clifford circuit corresponding to ``num``, where ``num`` is between 0 and 23. """unpacked=cls._unpack_num(num,cls.CLIFFORD_1_QUBIT_SIG)i,j,p=unpacked[0],unpacked[1],unpacked[2]qc=QuantumCircuit(1,name=f"Clifford-1Q({num})")ifi==1:qc.h(0)ifj==1:qc.sxdg(0)ifj==2:qc.s(0)ifp==1:qc.x(0)ifp==2:qc.y(0)ifp==3:qc.z(0)ifbasis_gates:qc=_synthesize_clifford_circuit(qc,basis_gates,synthesis_method=synthesis_method)returnqc
[docs]@classmethod@lru_cache(maxsize=11520)defclifford_2_qubit_circuit(cls,num,basis_gates:Optional[Tuple[str,...]]=None,coupling_tuple:Optional[Tuple[Tuple[int,int]]]=None,synthesis_method:str=DEFAULT_SYNTHESIS_METHOD,):"""Return the 2-qubit clifford circuit corresponding to `num` where `num` is between 0 and 11519. """qc=QuantumCircuit(2,name=f"Clifford-2Q({num})")forlayer,idxinenumerate(_layer_indices_from_num(num)):ifbasis_gates:layer_circ=_transformed_clifford_layer(layer,idx,basis_gates,coupling_tuple,synthesis_method=synthesis_method)else:layer_circ=_CLIFFORD_LAYER[layer][idx]_circuit_compose(qc,layer_circ,qubits=(0,1))returnqc
@staticmethoddef_unpack_num(num,sig):r"""Returns a tuple :math:`(a_1, \ldots, a_n)` where :math:`0 \le a_i \le \sigma_i` where sig=:math:`(\sigma_1, \ldots, \sigma_n)` and num is the sequential number of the tuple """res=[]forkinsig:res.append(num%k)num//=kreturnres
# Constant mapping from 1Q single Clifford gate to 1Q Clifford numerical identifier.# This table must be generated using `data.generate_clifford_data.gen_cliff_single_1q_gate_map`, or,# equivalently, correspond to the ordering implicitly defined by CliffUtils.clifford_1_qubit_circuit._CLIFF_SINGLE_GATE_MAP_1Q={("id",(0,)):0,("h",(0,)):1,("sxdg",(0,)):2,("s",(0,)):4,("x",(0,)):6,("sx",(0,)):8,("y",(0,)):12,("z",(0,)):18,("sdg",(0,)):22,}# Constant mapping from 2Q single Clifford gate to 2Q Clifford numerical identifier.# This table must be generated using `data.generate_clifford_data.gen_cliff_single_2q_gate_map`, or,# equivalently, correspond to the ordering defined by _layer_indices_from_num and _CLIFFORD_LAYER._CLIFF_SINGLE_GATE_MAP_2Q={("id",(0,)):0,("id",(1,)):0,("h",(0,)):5760,("h",(1,)):2880,("sxdg",(0,)):6720,("sxdg",(1,)):3200,("s",(0,)):7680,("s",(1,)):3520,("x",(0,)):4,("x",(1,)):1,("sx",(0,)):6724,("sx",(1,)):3201,("y",(0,)):8,("y",(1,)):2,("z",(0,)):12,("z",(1,)):3,("sdg",(0,)):7692,("sdg",(1,)):3523,("cx",(0,1)):16,("cx",(1,0)):2336,("cz",(0,1)):368,("cz",(1,0)):368,}######### Functions for 1-qubit integer Clifford operationsdefcompose_1q(lhs:Integral,rhs:Integral)->Integral:"""Return the composition of 1-qubit clifford integers."""return_CLIFFORD_COMPOSE_1Q[lhs,rhs]definverse_1q(num:Integral)->Integral:"""Return the inverse of a 1-qubit clifford integer."""return_CLIFFORD_INVERSE_1Q[num]defnum_from_1q_circuit(qc:QuantumCircuit)->Integral:"""Convert a given 1-qubit Clifford circuit to the corresponding integer. Note: The circuit must consist of gates in :const:`_CLIFF_SINGLE_GATE_MAP_1Q`, RZGate, Delay and Barrier. """num=0forinstinqc:rhs=_num_from_1q_gate(op=inst.operation)num=_CLIFFORD_COMPOSE_1Q[num,rhs]returnnumdef_num_from_1q_gate(op:Instruction)->int:""" Convert a given 1-qubit clifford operation to the corresponding integer. Note that supported operations are limited to ones in :const:`_CLIFF_SINGLE_GATE_MAP_1Q` or Rz gate. Args: op: operation to be converted. Returns: An integer representing a Clifford consisting of a single operation. Raises: QiskitError: If the input instruction is not a Clifford instruction. QiskitError: If rz is given with a angle that is not Clifford. """ifop.namein{"delay","barrier"}:return0try:name=_deparameterized_name(op)return_CLIFF_SINGLE_GATE_MAP_1Q[(name,(0,))]exceptQiskitErroraserr:raiseQiskitError(f"Parameterized instruction {op.name} could not be converted to integer Clifford")fromerrexceptKeyErroraserr:raiseQiskitError(f"Instruction {op.name} could not be converted to integer Clifford")fromerrdef_deparameterized_name(inst:Instruction)->str:ifinst.name=="rz":ifnp.isclose(inst.params[0],np.pi)ornp.isclose(inst.params[0],-np.pi):return"z"elifnp.isclose(inst.params[0],np.pi/2):return"s"elifnp.isclose(inst.params[0],-np.pi/2):return"sdg"else:raiseQiskitError(f"Wrong param {inst.params[0]} for rz in clifford")returninst.name######### Functions for 2-qubit integer Clifford operationsdefcompose_2q(lhs:Integral,rhs:Integral)->Integral:"""Return the composition of 2-qubit clifford integers."""num=lhsforlayer,idxinenumerate(_layer_indices_from_num(rhs)):gate_numbers=_CLIFFORD_LAYER_NUMS[layer][idx]forningate_numbers:num=_CLIFFORD_COMPOSE_2Q_DENSE[num,_clifford_num_to_dense_index[n]]returnnumdefinverse_2q(num:Integral)->Integral:"""Return the inverse of a 2-qubit clifford integer."""return_CLIFFORD_INVERSE_2Q[num]defnum_from_2q_circuit(qc:QuantumCircuit)->Integral:"""Convert a given 2-qubit Clifford circuit to the corresponding integer. Note: The circuit must consist of gates in :const:`_CLIFF_SINGLE_GATE_MAP_2Q`, RZGate, Delay and Barrier. """lhs=0forrhsin_clifford_2q_nums_from_2q_circuit(qc):lhs=_CLIFFORD_COMPOSE_2Q_DENSE[lhs,_clifford_num_to_dense_index[rhs]]returnlhsdef_num_from_2q_gate(op:Instruction,qubits:Optional[Union[Tuple[int,int],Tuple[int]]]=None)->int:""" Convert a given 1-qubit clifford operation to the corresponding integer. Note that supported operations are limited to ones in `_CLIFF_SINGLE_GATE_MAP_2Q` or Rz gate. Args: op: operation of instruction to be converted. qubits: qubits to which the operation applies Returns: An integer representing a Clifford consisting of a single operation. Raises: QiskitError: If the input instruction is not a Clifford instruction. QiskitError: If rz is given with a angle that is not Clifford. """ifop.namein{"delay","barrier"}:return0qubits=qubitsor(0,1)try:name=_deparameterized_name(op)return_CLIFF_SINGLE_GATE_MAP_2Q[(name,qubits)]exceptQiskitErroraserr:raiseQiskitError(f"Parameterized instruction {op.name} could not be converted to integer Clifford")fromerrexceptKeyErroraserr:raiseQiskitError(f"Instruction {op.name} on {qubits} could not be converted to integer Clifford")fromerrdef_append_v_w(qc,vw0,vw1):ifvw0=="v":qc.sdg(0)qc.h(0)elifvw0=="w":qc.h(0)qc.s(0)ifvw1=="v":qc.sdg(1)qc.h(1)elifvw1=="w":qc.h(1)qc.s(1)def_create_cliff_2q_layer_0():"""Layer 0 consists of 0 or 1 H gates on each qubit, followed by 0/1/2 V gates on each qubit. Number of Cliffords == 36."""circuits=[]num_h=[0,1]v_w_gates=["i","v","w"]forh0,h1,v0,v1initertools.product(num_h,num_h,v_w_gates,v_w_gates):qc=QuantumCircuit(2)for_inrange(h0):qc.h(0)for_inrange(h1):qc.h(1)_append_v_w(qc,v0,v1)circuits.append(qc)returncircuitsdef_create_cliff_2q_layer_1():"""Layer 1 consists of one of the following: - nothing - cx(0,1) followed by 0/1/2 V gates on each qubit - cx(0,1), cx(1,0) followed by 0/1/2 V gates on each qubit - cx(0,1), cx(1,0), cx(0,1) Number of Cliffords == 20."""circuits=[QuantumCircuit(2)]# identity at the beginningv_w_gates=["i","v","w"]forv0,v1initertools.product(v_w_gates,v_w_gates):qc=QuantumCircuit(2)qc.cx(0,1)_append_v_w(qc,v0,v1)circuits.append(qc)forv0,v1initertools.product(v_w_gates,v_w_gates):qc=QuantumCircuit(2)qc.cx(0,1)qc.cx(1,0)_append_v_w(qc,v0,v1)circuits.append(qc)qc=QuantumCircuit(2)# swap at the endqc.cx(0,1)qc.cx(1,0)qc.cx(0,1)circuits.append(qc)returncircuitsdef_create_cliff_2q_layer_2():"""Layer 2 consists of a Pauli gate on each qubit {Id, X, Y, Z}. Number of Cliffords == 16."""circuits=[]pauli=("i",XGate(),YGate(),ZGate())forp0,p1initertools.product(pauli,pauli):qc=QuantumCircuit(2)ifp0!="i":qc.append(p0,[0])ifp1!="i":qc.append(p1,[1])circuits.append(qc)returncircuits_CLIFFORD_LAYER=(_create_cliff_2q_layer_0(),_create_cliff_2q_layer_1(),_create_cliff_2q_layer_2(),)_NUM_LAYER_1=20_NUM_LAYER_2=16def_clifford_2q_nums_from_2q_circuit(qc:QuantumCircuit)->Iterable[Integral]:"""Yield Clifford numbers that represents the 2Q Clifford circuit."""forinstinqc:qubits=tuple(qc.find_bit(q).indexforqininst.qubits)yield_num_from_2q_gate(op=inst.operation,qubits=qubits)# Construct mapping from Clifford layers to series of Clifford numbers_CLIFFORD_LAYER_NUMS=[[tuple(_clifford_2q_nums_from_2q_circuit(qc))forqcin_CLIFFORD_LAYER[layer]]forlayerin[0,1,2]]@lru_cache(maxsize=256)def_transformed_clifford_layer(layer:int,index:Integral,basis_gates:Tuple[str,...],coupling_tuple:Optional[Tuple[Tuple[int,int]]],synthesis_method:str=DEFAULT_SYNTHESIS_METHOD,)->QuantumCircuit:# Return the index-th quantum circuit of the layer translated with the basis_gates.# The result is cached for speed.return_synthesize_clifford_circuit(_CLIFFORD_LAYER[layer][index],basis_gates=basis_gates,coupling_tuple=coupling_tuple,synthesis_method=synthesis_method,)def_num_from_layer_indices(triplet:Tuple[Integral,Integral,Integral])->Integral:"""Return the clifford number corresponding to the input triplet."""num=triplet[0]*_NUM_LAYER_1*_NUM_LAYER_2+triplet[1]*_NUM_LAYER_2+triplet[2]returnnumdef_layer_indices_from_num(num:Integral)->Tuple[Integral,Integral,Integral]:"""Return the triplet of layer indices corresponding to the input number."""idx2=num%_NUM_LAYER_2num=num//_NUM_LAYER_2idx1=num%_NUM_LAYER_1idx0=num//_NUM_LAYER_1returnidx0,idx1,idx2def_tensor_1q_nums(first:Integral,second:Integral)->Integral:"""Return the 2-qubit Clifford integer that is the tensor product of 1-qubit Cliffords."""return_CLIFFORD_TENSOR_1Q[first,second]