Skip to content

Commit

Permalink
Add layer fidelity experiment (#1322)
Browse files Browse the repository at this point in the history
### Summary
Add a new experiment class to measure layer fidelity, which is a
holistic benchmark to characterize the full quality of the devices at
scale (https://arxiv.org/abs/2311.05933)

Example notebook: [run_lf_qiskit_experiments_large.ipynb
(Gist)](https://gist.github.com/itoko/28c7cc117614c67e2a1899a3757d4ad1)

### Experimental features:
- Exceptionally `circuits()` method returns circuits on physical qubits
(not virtual qubits as usual)
- Add `reason` as an extra analysis result entry to tell users why the
`quality` of the analysis was "bad"

### Follow-up items:
- Add API for customizing DD (e.g. register DD sequence generator by
name and specify the name in experiment_options)
```
def dd_func1(delay_length, backend) ->  list[Instruction];

LayerFidelity.dd_functions = {
    "dd1": dd_func1,
    "dd2": dd_func2,
}
```

### Features decided not to include:
- `full_sampling` option (`StandardRB` has). `LayerFidelity` behaves as
if setting always `full_sampling==True` to avoid correlation between
sequences, which RB theory does not consider.
- `replicate_in_parallel` option that allows to use a common direct RB
sequence for all qubit pairs. It turned out that
`replicate_in_parallel==True` may underestimate some types of errors,
suggesting it should always be set to `False`.

### Issues to be addressed separately
- Poor interface for querying figures: No pointer to a relevant figure
(or data used for fitting) is stored in `AnalysisResult` (i.e. users who
find a bad fitting result in `AnalysisResult` cannot easily look at a
figure relevant to the result)
==> For now, you can query a figure by its name; e.g.
`exp_data.figure("DirectRB_Q83_Q82")`

### Dependencies:
- [x] #1288 for Clifford synthesis with unidirectional 2q gates (e.g.
IBM Eagle processors)
  • Loading branch information
itoko authored May 1, 2024
1 parent 19dd706 commit f352b3c
Show file tree
Hide file tree
Showing 9 changed files with 1,113 additions and 8 deletions.
2 changes: 1 addition & 1 deletion docs/manuals/measurement/readout_mitigation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Mitigation example
.. jupyter-execute::

qc = QuantumCircuit(num_qubits)
qc.h(0)
qc.sx(0)
for i in range(1, num_qubits):
qc.cx(i - 1, i)
qc.measure_all()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
StandardRB
InterleavedRB
LayerFidelity
Analysis
Expand All @@ -36,6 +37,7 @@
RBAnalysis
InterleavedRBAnalysis
LayerFidelityAnalysis
Synthesis
=========
Expand All @@ -61,3 +63,5 @@
from .clifford_utils import CliffordUtils
from .rb_utils import RBUtils
from .clifford_synthesis import RBDefaultCliffordSynthesis
from .layer_fidelity import LayerFidelity
from .layer_fidelity_analysis import LayerFidelityAnalysis
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
_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: ii for ii, idx in enumerate(_valid_sparse_indices)}
_CLIFFORD_TENSOR_1Q = np.load(f"{_DATA_FOLDER}/clifford_tensor_1q.npz")["table"]

# Transpilation utilities
def _transpile_clifford_circuit(
Expand Down Expand Up @@ -486,7 +487,11 @@ def inverse_1q(num: Integral) -> Integral:


def num_from_1q_circuit(qc: QuantumCircuit) -> Integral:
"""Convert a given 1-qubit Clifford circuit to the corresponding integer."""
"""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 = 0
for inst in qc:
rhs = _num_from_1q_gate(op=inst.operation)
Expand All @@ -497,7 +502,7 @@ def num_from_1q_circuit(qc: QuantumCircuit) -> Integral:
def _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.
Note that supported operations are limited to ones in :const:`_CLIFF_SINGLE_GATE_MAP_1Q` or Rz gate.
Args:
op: operation to be converted.
Expand Down Expand Up @@ -556,7 +561,11 @@ def inverse_2q(num: Integral) -> Integral:


def num_from_2q_circuit(qc: QuantumCircuit) -> Integral:
"""Convert a given 2-qubit Clifford circuit to the corresponding integer."""
"""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 = 0
for rhs in _clifford_2q_nums_from_2q_circuit(qc):
lhs = _CLIFFORD_COMPOSE_2Q_DENSE[lhs, _clifford_num_to_dense_index[rhs]]
Expand All @@ -568,7 +577,7 @@ def _num_from_2q_gate(
) -> 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.
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.
Expand Down Expand Up @@ -730,3 +739,8 @@ def _layer_indices_from_num(num: Integral) -> Tuple[Integral, Integral, Integral
idx1 = num % _NUM_LAYER_1
idx0 = num // _NUM_LAYER_1
return idx0, idx1, idx2


def _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]
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def _hash_cliff(cliff):


def gen_clifford_inverse_1q():
"""Generate table data for integer 1Q Clifford inversion"""
"""Generate data for integer 1Q Clifford inversion table"""
invs = np.empty(NUM_CLIFFORD_1Q, dtype=int)
for i, cliff_i in _CLIFF_1Q.items():
invs[i] = _TO_INT_1Q[_hash_cliff(cliff_i.adjoint())]
Expand All @@ -68,7 +68,7 @@ def gen_clifford_inverse_1q():


def gen_clifford_compose_1q():
"""Generate table data for integer 1Q Clifford composition."""
"""Generate data for integer 1Q Clifford composition table"""
products = np.empty((NUM_CLIFFORD_1Q, NUM_CLIFFORD_1Q), dtype=int)
for i, cliff_i in _CLIFF_1Q.items():
for j, cliff_j in _CLIFF_1Q.items():
Expand All @@ -83,7 +83,7 @@ def gen_clifford_compose_1q():


def gen_clifford_inverse_2q():
"""Generate table data for integer 2Q Clifford inversion"""
"""Generate data for integer 2Q Clifford inversion table"""
invs = np.empty(NUM_CLIFFORD_2Q, dtype=int)
for i, cliff_i in _CLIFF_2Q.items():
invs[i] = _TO_INT_2Q[_hash_cliff(cliff_i.adjoint())]
Expand Down Expand Up @@ -191,6 +191,16 @@ def gen_cliff_single_2q_gate_map():
return table


def gen_clifford_tensor_1q():
"""Generate data for 2Q integer Clifford table of the tensor product of 1Q integer Cliffords."""
products = np.empty((NUM_CLIFFORD_1Q, NUM_CLIFFORD_1Q), dtype=int)
for i, cliff_i in _CLIFF_1Q.items():
for j, cliff_j in _CLIFF_1Q.items():
cliff = cliff_i.tensor(cliff_j)
products[i, j] = _TO_INT_2Q[_hash_cliff(cliff)]
return products


if __name__ == "__main__":
if _CLIFF_SINGLE_GATE_MAP_1Q != gen_cliff_single_1q_gate_map():
raise Exception(
Expand All @@ -212,3 +222,5 @@ def gen_cliff_single_2q_gate_map():
table=_CLIFFORD_COMPOSE_2Q_DENSE,
valid_sparse_indices=valid_sparse_indices,
)

np.savez_compressed("clifford_tensor_1q.npz", table=gen_clifford_tensor_1q())
Loading

0 comments on commit f352b3c

Please sign in to comment.