Skip to content

Commit

Permalink
Add more validations
Browse files Browse the repository at this point in the history
  • Loading branch information
itoko committed Feb 14, 2024
1 parent 40518d6 commit 757909e
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from numpy.random import Generator, default_rng
from numpy.random.bit_generator import BitGenerator, SeedSequence

from qiskit.circuit import QuantumCircuit, CircuitInstruction, Barrier
from qiskit.circuit import QuantumCircuit, CircuitInstruction, Barrier, Gate
from qiskit.circuit.library import get_standard_gate_name_mapping
from qiskit.exceptions import QiskitError
from qiskit.providers import BackendV2Converter
Expand Down Expand Up @@ -50,7 +50,6 @@
LOG = logging.getLogger(__name__)


GATE_NAME_MAP = get_standard_gate_name_mapping()
NUM_1Q_CLIFFORD = CliffordUtils.NUM_CLIFFORD_1_QUBIT


Expand All @@ -74,12 +73,11 @@ def __init__(
two_qubit_layers: Sequence[Sequence[Tuple[int, int]]],
lengths: Iterable[int],
backend: Optional[Backend] = None,
num_samples: int = 3,
num_samples: int = 6,
seed: Optional[Union[int, SeedSequence, BitGenerator, Generator]] = None,
# full_sampling: Optional[bool] = True, TODO: can we remove this option?
two_qubit_gate: Optional[str] = None,
one_qubit_basis_gates: Optional[Sequence[str]] = None,
replicate_in_parallel: bool = True,
replicate_in_parallel: bool = False, # TODO: remove this option
):
"""Initialize a standard randomized benchmarking experiment.
Expand All @@ -105,32 +103,63 @@ def __init__(
full_layers = []
for two_q_layer in two_qubit_layers:
qubits_in_layer = {q for qpair in two_q_layer for q in qpair}
for q in qubits_in_layer:
if q not in physical_qubits:
raise QiskitError(f"Qubit {q} in two_qubit_layers is not in physical_qubits")
layer = two_q_layer + [(q,) for q in physical_qubits if q not in qubits_in_layer]
full_layers.append(layer)

# Initialize base experiment
super().__init__(
physical_qubits, analysis=LayerFidelityAnalysis(full_layers), backend=backend
)
# assert isinstance(backend, BackendV2)

# Verify parameters
# TODO more checks
if len(set(lengths)) != len(lengths):
raise QiskitError(
f"The lengths list {lengths} should not contain " "duplicate elements."
)
raise QiskitError(f"The lengths list {lengths} should not contain duplicate elements.")
if num_samples <= 0:
raise QiskitError(f"The number of samples {num_samples} should " "be positive.")
if two_qubit_gate not in GATE_NAME_MAP:
pass # TODO: too restrictive to forbidden custom two qubit gate name?

# Get parameters from backend
if two_qubit_gate is None:
# TODO: implement and raise an error if backend is None
raise NotImplemented()
if one_qubit_basis_gates is None:
# TODO: implement and raise an error if backend is None
raise NotImplemented()
raise QiskitError(f"The number of samples {num_samples} should be positive.")

if two_qubit_gate:
if self.backend:
if two_qubit_gate not in self.backend.target.operation_names:
raise QiskitError(f"two_qubit_gate {two_qubit_gate} is not in backend.target")
for two_q_layer in two_qubit_layers:
for qpair in two_q_layer:
if not self.backend.target.instruction_supported(two_qubit_gate, qpair):
raise QiskitError(f"{two_qubit_gate}{qpair} is not in backend.target")
else:
if self.backend:
# Try to set default two_qubit_gate from backend
for op in self.backend.target.operations:
if isinstance(op, Gate) and op.num_qubits == 2:
two_qubit_gate = op.name
LOG.info("%s is set for two_qubit_gate", op.name)
break
if not two_qubit_gate:
raise QiskitError(f"two_qubit_gate is not provided and failed to set from backend.")

if one_qubit_basis_gates:
for gate in one_qubit_basis_gates:
if gate not in self.backend.target.operation_names:
raise QiskitError(f"{gate} in one_qubit_basis_gates is not in backend.target")
for gate in one_qubit_basis_gates:
for q in self.physical_qubits:
if not self.backend.target.instruction_supported(gate, (q,)):
raise QiskitError(f"{gate}({q}) is not in backend.target")
else:
if self.backend:
# Try to set default one_qubit_basis_gates from backend
one_qubit_basis_gates = []
for op in self.backend.target.operations:
if isinstance(op, Gate) and op.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))
if not one_qubit_basis_gates:
raise QiskitError(
f"one_qubit_basis_gates is not provided and failed to set from backend."
)

# Set configurable options
self.set_experiment_options(
Expand All @@ -142,7 +171,6 @@ def __init__(
one_qubit_basis_gates=tuple(one_qubit_basis_gates),
replicate_in_parallel=replicate_in_parallel,
)
# self.analysis.set_options(outcome="0" * self.num_qubits)

@classmethod
def _default_experiment_options(cls) -> Options:
Expand Down Expand Up @@ -211,10 +239,10 @@ def set_transpile_options(self, **fields):
)

def _set_backend(self, backend: Backend):
"""Set the backend V2 for RB experiments since RB experiments only support BackendV2
except for simulators. If BackendV1 is provided, it is converted to V2 and stored.
"""Set the backend V2 for RB experiments since RB experiments only support BackendV2.
If BackendV1 is provided, it is converted to V2 and stored.
"""
if isinstance(backend, BackendV1) and "simulator" not in backend.name():
if isinstance(backend, BackendV1):
super()._set_backend(BackendV2Converter(backend, add_delay=True))
else:
super()._set_backend(backend)
Expand Down Expand Up @@ -252,7 +280,7 @@ def circuits_generator(self) -> Iterable[QuantumCircuit]:
coupling_tuple=((0, 1),),
synthesis_method=opts.clifford_synthesis_method,
)
GATE2Q = GATE_NAME_MAP[opts.two_qubit_gate]
GATE2Q = self.backend.target.operation_from_name(opts.two_qubit_gate)
GATE2Q_CLIFF = num_from_2q_circuit(Clifford(GATE2Q).to_circuit())
# Circuit generation
circuits = []
Expand Down Expand Up @@ -458,9 +486,6 @@ def _transpiled_circuits(self) -> List[QuantumCircuit]:
common_calibrations[op_name][(qargs, tuple())] = schedule

for circ in transpiled:
# This logic is inefficient in terms of payload size and backend compilation
# because this binds every custom pulse to a circuit regardless of
# its existence. It works but redundant calibration must be removed -- NK.
circ.calibrations = common_calibrations

return transpiled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def _create_analysis_results(

# Calculate process fidelity
alpha = fit_data.ufloat_params["alpha"]
pf = (1 + (d**2 - 1) * alpha) / (d**2)
pf = (1 + (d * d - 1) * alpha) / (d * d)

quality, reason = self.__evaluate_quality(fit_data)

Expand All @@ -161,10 +161,6 @@ def _create_analysis_results(
def _run_analysis(
self, experiment_data: ExperimentData
) -> Tuple[List[AnalysisResultData], List["matplotlib.figure.Figure"]]:
r"""TODO
Note: Empty analysis results will be returned when failing analysis.
"""
try:
return super()._run_analysis(experiment_data)
except: # pylint: disable=broad-except
Expand Down Expand Up @@ -221,16 +217,16 @@ def __evaluate_quality(
class _SingleLayerFidelityAnalysis(CompositeAnalysis):
r"""A class to estimate a process fidelity per disjoint layer.
Note: Empty analysis results will be returned when failing analysis.
TODO: Add math.
# section: reference
.. ref_arxiv:: 1 2311.05933
"""

def __init__(self, layer, analyses=None):
if analyses:
# TODO: Validation
pass
if len(layer) != len(analyses):
raise AnalysisError(f"'analyses' must have the same length with 'layer'")
else:
analyses = [_ProcessFidelityAnalysis(qubits) for qubits in layer]

Expand All @@ -240,7 +236,6 @@ def __init__(self, layer, analyses=None):
def _run_analysis(
self, experiment_data: ExperimentData
) -> Tuple[List[AnalysisResultData], List["matplotlib.figure.Figure"]]:
r"""TODO"""
try:
# Run composite analysis and extract sub-experiments results
analysis_results, figures = super()._run_analysis(experiment_data)
Expand Down Expand Up @@ -286,8 +281,8 @@ class LayerFidelityAnalysis(CompositeAnalysis):

def __init__(self, layers, analyses=None):
if analyses:
# TODO: Validation
pass
if len(layers) != len(analyses):
raise AnalysisError(f"'analyses' must have the same length with 'layers'")
else:
analyses = [_SingleLayerFidelityAnalysis(a_layer) for a_layer in layers]

Expand All @@ -299,9 +294,21 @@ def _run_analysis(
self, experiment_data: ExperimentData
) -> Tuple[List[AnalysisResultData], List["matplotlib.figure.Figure"]]:
r"""Run analysis for Layer Fidelity experiment.
It invokes CompositeAnalysis._run_analysis that will invoke
_run_analysis for the sub-experiments (1Q/2Q simultaneous direct RBs).
Based on the results, it computes the result for Layer Fidelity.
It invokes :meth:`CompositeAnalysis._run_analysis` that will invoke
``_run_analysis`` for the sub-experiments (1Q/2Q simultaneous direct RBs for each layer).
Based on the results, it computes Layer Fidelity and EPLG (error per layered gate).
TODO: Add math.
Args:
experiment_data: the experiment data to analyze.
Returns:
A pair ``(analysis_results, figures)`` where ``analysis_results``
is a list of :class:`AnalysisResultData` objects, and ``figures``
is a list of any figures for the experiment.
If an analysis fails, an analysis result with ``None`` value will be returned.
"""
try:
# Run composite analysis and extract sub-experiments results
Expand Down

0 comments on commit 757909e

Please sign in to comment.