Source code for qiskit_experiments.library.randomized_benchmarking.layer_fidelity
# This code is part of Qiskit.## (C) Copyright IBM 2023.## 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."""Layer Fidelity RB Experiment class."""importfunctoolsimportloggingfromcollectionsimportdefaultdictfromtypingimportUnion,Iterable,Optional,List,Sequence,Tuple,Dictfromnumpy.randomimportGenerator,default_rngfromnumpy.random.bit_generatorimportBitGenerator,SeedSequencefromqiskit.circuitimportQuantumCircuit,CircuitInstruction,Barrier,Gatefromqiskit.circuit.libraryimportget_standard_gate_name_mappingfromqiskit.exceptionsimportQiskitErrorfromqiskit.providersimportBackendV2Converterfromqiskit.providers.backendimportBackend,BackendV1,BackendV2fromqiskit.quantum_infoimportCliffordfromqiskit.pulse.instruction_schedule_mapimportCalibrationPublisherfromqiskit_experiments.frameworkimportBaseExperiment,Optionsfromqiskit_experiments.framework.configsimportExperimentConfigfromqiskit_experiments.framework.restless_mixinimportRestlessMixinfrom.clifford_utilsimport(CliffordUtils,DEFAULT_SYNTHESIS_METHOD,compose_1q,compose_2q,inverse_1q,inverse_2q,num_from_2q_circuit,_tensor_1q_nums,_clifford_1q_int_to_instruction,_clifford_2q_int_to_instruction,_decompose_clifford_ops,)from.layer_fidelity_analysisimportLayerFidelityAnalysisLOG=logging.getLogger(__name__)GATE_NAME_MAP=get_standard_gate_name_mapping()NUM_1Q_CLIFFORD=CliffordUtils.NUM_CLIFFORD_1_QUBIT
[docs]classLayerFidelity(BaseExperiment,RestlessMixin):r"""A holistic benchmarking experiment to characterize the full quality of the devices at scale. # section: overview Layer Fidelity (LF) is a method to estimate the fidelity of a connecting set of two-qubit gates over :math:`N` qubits by measuring gate errors using simultaneous direct randomized benchmarking (RB) in disjoint layers. LF can easily be expressed as a layer size independent quantity, error per layered gate (EPLG): :math:`EPLG = 1 - LF^{1/N_{2Q}}` where :math:`N_{2Q}` is number of 2-qubit gates in the layers. Each of the 2-qubit (or 1-qubit) direct RBs yields the decaying probabilities to get back to the ground state for an increasing sequence length (i.e. number of layers), fits the exponential curve to estimate the decay rate, and calculates the process fidelity of the subsystem from the rate. LF is calculated as the product of the 2-qubit (or 1-qubit) process fidelities. See Ref. [1] for details. # section: analysis_ref :class:`LayerFidelityAnalysis` # section: reference .. ref_arxiv:: 1 2311.05933 # section: example .. jupyter-execute:: :hide-code: # backend from qiskit_aer import AerSimulator from qiskit_ibm_runtime.fake_provider import FakePerth backend = AerSimulator.from_backend(FakePerth()) .. jupyter-execute:: import numpy as np from qiskit_experiments.library import StandardRB from qiskit_experiments.library.randomized_benchmarking import LayerFidelity lengths = np.arange(1, 800, 200) two_qubit_layers=[[(0, 1), (3, 5)], [(1, 3), (5, 6)]] num_samples = 6 seed = 106 exp = LayerFidelity( physical_qubits=[0, 1, 3, 5, 6], two_qubit_layers=two_qubit_layers, lengths=lengths, backend=backend, num_samples=num_samples, seed=seed, two_qubit_gate=None, one_qubit_basis_gates=None, ) exp_data = exp.run().block_for_results() results = exp_data.analysis_results() display(exp_data.figure(0)) # one of 6 figures display(exp_data.analysis_results("EPLG", dataframe=True)) names={result.name for result in results} print(f"Available results: {names}") """def__init__(self,physical_qubits:Sequence[int],two_qubit_layers:Sequence[Sequence[Tuple[int,int]]],lengths:Iterable[int],backend:Optional[Backend]=None,num_samples:int=6,seed:Optional[Union[int,SeedSequence,BitGenerator,Generator]]=None,two_qubit_gate:Optional[str]=None,one_qubit_basis_gates:Optional[Sequence[str]]=None,):"""Initialize a layer fidelity experiment. Args: physical_qubits: List of physical qubits for the experiment. two_qubit_layers: List of two-qubit gate layers to run on. Each two-qubit gate layer must be given as a list of directed qubit pairs. lengths: A list of layer lengths (the number of depth points). backend: The backend to run the experiment on. Note that either ``backend`` or ``two_qubit_gate`` and ``one_qubit_basis_gates`` must be set at instantiation. num_samples: Number of samples (i.e. circuits) to generate for each layer length. seed: Optional, seed used to initialize ``numpy.random.default_rng``. when generating circuits. The ``default_rng`` will be initialized with this seed value every time :meth:~.LayerFidelity.circuits` is called. two_qubit_gate: Optional, 2q-gate name (e.g. "cx", "cz", "ecr") of which the two qubit layers consist. If not specified (but ``backend`` is supplied), one of 2q-gates supported in the backend is automatically set. one_qubit_basis_gates: Optional, 1q-gates to use for implementing 1q-Clifford operations. If not specified (but ``backend`` is supplied), all 1q-gates supported in the backend are automatically set. Raises: QiskitError: If any invalid argument is supplied. """# Compute full layersfull_layers=[]fortwo_q_layerintwo_qubit_layers:qubits_in_layer={qforqpairintwo_q_layerforqinqpair}iflen(qubits_in_layer)!=2*len(two_q_layer):raiseQiskitError("two_qubit_layers have a layer with gates on non-disjoint qubits")forqinqubits_in_layer:ifqnotinphysical_qubits:raiseQiskitError(f"Qubit {q} in two_qubit_layers is not in physical_qubits")layer=two_q_layer+[(q,)forqinphysical_qubitsifqnotinqubits_in_layer]full_layers.append(layer)# Initialize base experimentsuper().__init__(physical_qubits,analysis=LayerFidelityAnalysis(full_layers),backend=backend)# assert isinstance(backend, BackendV2)# Verify parametersiflen(set(lengths))!=len(lengths):raiseQiskitError(f"The lengths list {lengths} should not contain duplicate elements.")ifnum_samples<=0:raiseQiskitError(f"The number of samples {num_samples} should be positive.")iftwo_qubit_gateisNone:ifself.backendisNone:raiseQiskitError("two_qubit_gate or backend must be supplied.")# Try to set default two_qubit_gate from backendforopinself.backend.target.operations:ifisinstance(op,Gate)andop.num_qubits==2:two_qubit_gate=op.nameLOG.info("%s is set for two_qubit_gate",op.name)breakifnottwo_qubit_gate:raiseQiskitError("two_qubit_gate is not provided and failed to set from backend.")else:ifself.backendisNoneandtwo_qubit_gatenotinGATE_NAME_MAP:raiseQiskitError(f"Unknown two_qubit_gate: {two_qubit_gate}.")ifone_qubit_basis_gatesisNone:ifself.backendisNone:raiseQiskitError("one_qubit_basis_gates or backend must be supplied.")# Try to set default one_qubit_basis_gates from backendone_qubit_basis_gates=[]foropinself.backend.target.operations:ifisinstance(op,Gate)andop.num_qubits==1:one_qubit_basis_gates.append(op.name)LOG.info("%s is set for one_qubit_basis_gates",str(one_qubit_basis_gates))ifnotone_qubit_basis_gates:raiseQiskitError("one_qubit_basis_gates is not provided and failed to set from backend.")else:ifself.backendisNone:forgateinone_qubit_basis_gates:ifgatenotinGATE_NAME_MAP:raiseQiskitError(f"Unknown gate in one_qubit_basis_gates: {gate}.")# Set configurable optionsself.set_experiment_options(lengths=sorted(lengths),num_samples=num_samples,seed=seed,two_qubit_layers=two_qubit_layers,two_qubit_gate=two_qubit_gate,one_qubit_basis_gates=tuple(one_qubit_basis_gates),)# Verify two_qubit_gate and one_qubit_basis_gatesself.__validate_basis_gates()@classmethoddef_default_experiment_options(cls)->Options:"""Default experiment options. Experiment Options: two_qubit_layers (List[List[Tuple[int, int]]]): List of two-qubit gate layers to run on. Each two-qubit gate layer must be given as a list of directed qubit pairs. lengths (List[int]): A list of layer lengths. num_samples (int): Number of samples to generate for each layer length. seed (None or int or SeedSequence or BitGenerator or Generator): A seed used to initialize ``numpy.random.default_rng`` when generating circuits. The ``default_rng`` will be initialized with this seed value every time :meth:`circuits` is called. two_qubit_gate (str): Two-qubit gate name (e.g. "cx", "cz", "ecr") of which the two qubit layers consist. one_qubit_basis_gates (Tuple[str]): One-qubit gates to use for implementing 1q Cliffords. clifford_synthesis_method (str): The name of the Clifford synthesis plugin to use for building circuits of RB sequences. """options=super()._default_experiment_options()options.update_options(lengths=None,num_samples=None,seed=None,two_qubit_layers=None,two_qubit_gate=None,one_qubit_basis_gates=None,clifford_synthesis_method=DEFAULT_SYNTHESIS_METHOD,)returnoptions
[docs]defset_experiment_options(self,**fields):"""Set the experiment options. Args: fields: The fields to update the options Raises: AttributeError: If the field passed in is not a supported options """forfieldinfields:iffieldin{"two_qubit_layers"}:if(hasattr(self._experiment_options,field)andself._experiment_options[field]isnotNone):raiseAttributeError(f"Options field {field} is not allowed to update.")super().set_experiment_options(**fields)
@classmethoddef_default_transpile_options(cls)->Options:"""Default transpiler options for transpiling RB circuits."""returnOptions(optimization_level=1)
[docs]defset_transpile_options(self,**fields):"""Transpile options is not supported for LayerFidelity experiments. Raises: QiskitError: If `set_transpile_options` is called. """raiseQiskitError("Custom transpile options is not supported for LayerFidelity experiments.")
def_set_backend(self,backend:Backend):"""Set the backend V2 for RB experiments since RB experiments only support BackendV2. If BackendV1 is provided, it is converted to V2 and stored. """ifisinstance(backend,BackendV1):super()._set_backend(BackendV2Converter(backend,add_delay=True))else:super()._set_backend(backend)self.__validate_basis_gates()def__validate_basis_gates(self)->None:ifnotself.backend:returnopts=self.experiment_options# validate two_qubit_gate if it is setifopts.two_qubit_gate:ifopts.two_qubit_gatenotinself.backend.target.operation_names:raiseQiskitError(f"two_qubit_gate {opts.two_qubit_gate} is not in backend.target")fortwo_q_layerinopts.two_qubit_layers:forqpairintwo_q_layer:ifnotself.backend.target.instruction_supported(opts.two_qubit_gate,qpair):raiseQiskitError(f"{opts.two_qubit_gate}{qpair} is not in backend.target")# validate one_qubit_basis_gates if it is setforgateinopts.one_qubit_basis_gatesor[]:ifgatenotinself.backend.target.operation_names:raiseQiskitError(f"{gate} in one_qubit_basis_gates is not in backend.target")forgateinopts.one_qubit_basis_gatesor[]:forqinself.physical_qubits:ifnotself.backend.target.instruction_supported(gate,(q,)):raiseQiskitError(f"{gate}({q}) is not in backend.target")def__residual_qubits(self,two_qubit_layer):qubits_in_layer={qforqpairintwo_qubit_layerforqinqpair}return[qforqinself.physical_qubitsifqnotinqubits_in_layer]
[docs]defcircuits(self)->List[QuantumCircuit]:r"""Return a list of physical circuits to measure layer fidelity. Returns: A list of :class:`QuantumCircuit`\s. """returnlist(self.circuits_generator())
[docs]defcircuits_generator(self)->Iterable[QuantumCircuit]:r"""Return a generator of physical circuits to measure layer fidelity. Returns: A generator of :class:`QuantumCircuit`\s. """opts=self.experiment_optionsresidal_qubits_by_layer=[self.__residual_qubits(layer)forlayerinopts.two_qubit_layers]rng=default_rng(seed=opts.seed)# define functions and variables for speed_to_gate_1q=functools.partial(_clifford_1q_int_to_instruction,basis_gates=opts.one_qubit_basis_gates,synthesis_method=opts.clifford_synthesis_method,)_to_gate_2q=functools.partial(_clifford_2q_int_to_instruction,basis_gates=(opts.two_qubit_gate,)+opts.one_qubit_basis_gates,coupling_tuple=((0,1),),synthesis_method=opts.clifford_synthesis_method,)ifself.backend:gate2q=self.backend.target.operation_from_name(opts.two_qubit_gate)else:gate2q=GATE_NAME_MAP[opts.two_qubit_gate]gate2q_cliff=num_from_2q_circuit(Clifford(gate2q).to_circuit())# Circuit generationnum_qubits=max(self.physical_qubits)+1fori_sampleinrange(opts.num_samples):fori_set,(two_qubit_layer,one_qubits)inenumerate(zip(opts.two_qubit_layers,residal_qubits_by_layer)):num_2q_gates=len(two_qubit_layer)num_1q_gates=len(one_qubits)composite_qubits=two_qubit_layer+[(q,)forqinone_qubits]composite_clbits=[(2*c,2*c+1)forcinrange(num_2q_gates)]composite_clbits.extend([(c,)forcinrange(2*num_2q_gates,2*num_2q_gates+num_1q_gates)])forlengthinopts.lengths:circ=QuantumCircuit(num_qubits,num_qubits)barrier_inst=CircuitInstruction(Barrier(num_qubits),circ.qubits)self.__circuit_body(circ,length,two_qubit_layer,one_qubits,rng,_to_gate_1q,_to_gate_2q,gate2q,gate2q_cliff,barrier_inst,)# add the measurementscirc._append(barrier_inst)forqubits,clbitsinzip(composite_qubits,composite_clbits):circ.measure(qubits,clbits)# store composite structure in metadatacirc.metadata={"experiment_type":"BatchExperiment","composite_metadata":[{"experiment_type":"ParallelExperiment","composite_index":list(range(len(composite_qubits))),"composite_metadata":[{"experiment_type":"SubLayerFidelity","physical_qubits":qpair,"sample":i_sample,"xval":length,}forqpairintwo_qubit_layer]+[{"experiment_type":"SubLayerFidelity","physical_qubits":(q,),"sample":i_sample,"xval":length,}forqinone_qubits],"composite_qubits":composite_qubits,"composite_clbits":composite_clbits,}],"composite_index":[i_set],}yieldcirc
@staticmethoddef__circuit_body(circ,length,two_qubit_layer,one_qubits,rng,_to_gate_1q,_to_gate_2q,gate2q,gate2q_cliff,barrier_inst,):# initialize cliffords and a ciruit (0: identity clifford)cliffs_2q=[0]*len(two_qubit_layer)cliffs_1q=[0]*len(one_qubits)for_inrange(length):# sample random 1q-Clifford layerforj,qpairinenumerate(two_qubit_layer):# sample product of two 1q-Cliffords as 2q interger Cliffordsamples=rng.integers(NUM_1Q_CLIFFORD,size=2)cliffs_2q[j]=compose_2q(cliffs_2q[j],_tensor_1q_nums(*samples))# For Clifford 1 (x) Clifford 2, in its circuit representation,# Clifford 1 acts on the 2nd qubit and Clifford 2 acts on the 1st qubit.# That's why the qpair is reversed here.forsample,qinzip(samples,reversed(qpair)):circ._append(_to_gate_1q(sample),(circ.qubits[q],),())fork,qinenumerate(one_qubits):sample=rng.integers(NUM_1Q_CLIFFORD)cliffs_1q[k]=compose_1q(cliffs_1q[k],sample)circ._append(_to_gate_1q(sample),(circ.qubits[q],),())circ._append(barrier_inst)# add two qubit gatesforj,qpairinenumerate(two_qubit_layer):circ._append(gate2q,tuple(circ.qubits[q]forqinqpair),())cliffs_2q[j]=compose_2q(cliffs_2q[j],gate2q_cliff)# TODO: add dd if necessaryfork,qinenumerate(one_qubits):# TODO: add dd if necessarypasscirc._append(barrier_inst)# add the last inverseforj,qpairinenumerate(two_qubit_layer):inv=inverse_2q(cliffs_2q[j])circ._append(_to_gate_2q(inv),tuple(circ.qubits[q]forqinqpair),())fork,qinenumerate(one_qubits):inv=inverse_1q(cliffs_1q[k])circ._append(_to_gate_1q(inv),(circ.qubits[q],),())returncircdef_transpiled_circuits(self)->List[QuantumCircuit]:"""Return a list of experiment circuits, transpiled."""transpiled=[_decompose_clifford_ops(circ)forcircinself.circuits()]# Set custom calibrations provided in backendifisinstance(self.backend,BackendV2):instructions=[]# (op_name, qargs) for each element where qargs mean qubit tuplefortwo_qubit_layerinself.experiment_options.two_qubit_layers:forqpairintwo_qubit_layer:instructions.append((self.experiment_options.two_qubit_gate,tuple(qpair)))forqinself.physical_qubits:forgate_1qinself.experiment_options.one_qubit_basis_gates:instructions.append((gate_1q,(q,)))common_calibrations=defaultdict(dict)forop_name,qargsininstructions:inst_prop=self.backend.target[op_name].get(qargs,None)ifinst_propisNone:continueschedule=inst_prop.calibrationifscheduleisNone:continuepublisher=schedule.metadata.get("publisher",CalibrationPublisher.QISKIT)ifpublisher!=CalibrationPublisher.BACKEND_PROVIDER:common_calibrations[op_name][(qargs,tuple())]=scheduleforcircintranspiled:circ.calibrations=common_calibrationsreturntranspileddef_metadata(self):metadata=super()._metadata()metadata["two_qubit_layers"]=self.experiment_options.two_qubit_layersreturnmetadata
[docs]@classmethoddeffrom_config(cls,config:Union[ExperimentConfig,Dict])->"LayerFidelity":"""Initialize an experiment from experiment config"""ifisinstance(config,dict):config=ExperimentConfig(**dict)ret=cls(*config.args,**config.kwargs)ifconfig.run_options:ret.set_run_options(**config.run_options)returnret