-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve integer-based Clifford operations for 1Q/2Q RB #940
Improve integer-based Clifford operations for 1Q/2Q RB #940
Conversation
Co-authored-by: merav-aharoni <[email protected]> Including following features: * Simplify clifford_utils API * Clean up script file for clifford data generation * Change clifford data files to npz formant and add them to package * Simplify data handling for 2q clifford compose with sparse matrix
953ec40
to
6c1d9ad
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks fine. The usage of npz
files is a nice improvement since they do not harm performance.
I wrote a few small comments.
@@ -142,16 +132,7 @@ def _clifford_1q_int_to_instruction( | |||
def _clifford_2q_int_to_instruction( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't this method part of CliffordUtils? Same for _clifford_1q_int_to_instruction
? Same for all the methods at the bottom.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just prefer function API to class method API for this utility module because we have no need to create CliffordUtils
objects. The class just behave as a module name. That means it can be replaced with import clifford_utils as clu
. Any comment, @nkanazawa1989 ? Should we deprecate CliffordUtils class if we move to the function API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree. CliffordUtils
can be promoted to an actual generator, but these function can be excluded if not intended to be used as a part of the generator (probably this one can be CliffordUtils
method, because it takes basis gates and likely the basis gates are instance variable of the proper RB circuit generator).
self, num_qubits: int, size: int = 1, rng: Optional[Union[int, Generator]] = None | ||
): | ||
"""Generate a list of random clifford circuits""" | ||
if num_qubits > 2: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if num_qubits > 2: | |
if num_qubits ==1: | |
samples = rng.integers(CliffordUtils.NUM_CLIFFORD_1_QUBIT, size=size) | |
return [self.clifford_1_qubit_circuit(i) for i in samples] | |
else if num_qubits ==2: | |
samples = rng.integers(CliffordUtils.NUM_CLIFFORD_2_QUBIT, size=size) | |
return [self.clifford_2_qubit_circuit(i) for i in samples] | |
else: # num_qubits > 2 | |
return [random_clifford(num_qubits, seed=rng).to_circuit() for _ in range(size)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don't catch the point of your suggestion. I've just restored the original code before #892 here. Did you find any issue in the original code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not a big issue. I just meant the ordering of the if
statements. It seems more natural to me to write:
if n == 1:
do 1
if n == 2:
do 2
else:
do all other cases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, looking around it, I found two bugs in ClifforUtils.random_cliffords
and ClifforUtils.random_clifford_circuits
in the original code... I've fixed them in 01722e2 and decided to deprecate them since they are not used anywhere at least within qiskit-experiments.
qiskit_experiments/library/randomized_benchmarking/clifford_utils.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/clifford_utils.py
Outdated
Show resolved
Hide resolved
def __compose_clifford( | ||
self, left_elem: SequenceElementType, right_elem: SequenceElementType | ||
) -> SequenceElementType: | ||
if self.num_qubits <= 2: | ||
utils = self._cliff_utils | ||
return utils.compose_num_with_clifford( | ||
left_elem, utils.create_cliff_from_num(right_elem) | ||
) | ||
|
||
if self.num_qubits == 1: | ||
return compose_1q(left_elem, right_elem) | ||
if self.num_qubits == 2: | ||
return compose_2q(left_elem, right_elem) | ||
return left_elem.compose(right_elem) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this method be part of CliffordUtils
? Same for __adjoint_clifford
qiskit_experiments/library/randomized_benchmarking/rb_experiment.py
Outdated
Show resolved
Hide resolved
"""Check that all 2 clifford numbers form a permutation over [0, 11519]""" | ||
cliff_utils = CliffordUtils(num_qubits=2, basis_gates=self.basis_gates) | ||
cliff_utils.transpile_2q_cliff_layers() | ||
self.is_permutation(cliff_utils.NUM_CLIFFORD_2_QUBIT, CLIFF_LAYERS_TO_NUM_2Q) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing that rows for a permutation was requested by @ShellyGarion in her initial review of my code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understand, this test was necessary to check the validity of CLIFF_LAYERS_TO_NUM_2Q
that defines another mapping between number and Clifford than a mapping defined in CliffordUtils.clifford_2_qubit
.
I've removed CLIFF_LAYERS_TO_NUM_2Q
and changed to have only one mapping between number and Clifford, so I think we don't need the test any longer (this round-trip test would be sufficient to check the validity of the mapping).
I'll let users know the change in number-2qClifford mapping via release note.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure I fully understand the new mapping, so maybe I am mistaken. For 1 qubit, we expect the mapping of all Cliffords to contain all the numbers 0-23 in some permutation. Is this still correct? Similarly for 2 qubits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. It must be correct.
For 1Q case, test_clifford_1_qubit_generation
directly guarantees that [0..23] yields all possible 24 Cliffords.
For 2Q case, test_number_to_clifford_mapping_2q
effectively guarantees that [0..11519] yields all possible 11520 Cliffords. Instead of enumerating all symplectic stabilizer tables, I used the round trip test (number i -> Clifford -> number i for all i in [0..11519]). Its success suggests all possible 11520 Cliffords are generated.
…erleaving circuit with delay using BackendV1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @itoko san this looks nice. Likely this is the minimum module import overhead because transpile is now on the fly with cache and cache is generated over triplets. Numpy data is good choice of data format though I have bit concern about persistency of the data because this is not version controlled.
_CLIFF_SINGLE_GATE_MAP_2Q, | ||
) | ||
|
||
NUM_CLIFFORD_1Q = CliffordUtils.NUM_CLIFFORD_1_QUBIT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name is bit confusing because we often say number to indicate circuit depth. Perhaps SIZE_CLIFFORD_GROUP_1Q
? Same for others.
"""Produce a hashable value that is unique for each different Clifford. This should only be | ||
used internally when the classes being hashed are under our control, because classes of this | ||
type are mutable.""" | ||
table = cliff.table |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked the Clifford class and likely they only compare .tableau
property. Why do you implement custom code for equality?
According to the stack overflow string conversion is the common approach for hash. This does not quite affect RB experiment performance though.
https://stackoverflow.com/questions/16589791/most-efficient-property-to-hash-for-numpy-array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good questions, actually, I thought both of them and converged to the current code.
Regarding tableau
, it's quite new, i.e. introduced in the latest terra 0.22. If we use the property, experiments must require terra >= 0.22. I think it would be better to use table
property until it is deprecated. But I know this code is only for developers to use (so far), so the requirement might not be so demanding (the requirement would live in requirements-dev.txt
). Which do you like?
The code implementing hash originated from Jake's PR in terra. I like the code because it should be more efficient than the general approach that uses string conversion in this particular case (we never call hash
since we need to create a hash without collision).
raise Exception( | ||
"_CLIFF_SINGLE_GATE_MAP_1Q must be generated by gen_cliff_single_1q_gate_map()" | ||
) | ||
np.savez_compressed("clifford_inverse_1q.npz", table=gen_clifford_inverse_1q()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think there is any risks of version dependency? Conversion of circuit -> Clifford instance currently has dependency on terra, and index is indirectly controlled in the CliffordUtils. If someone update these code, do you have a mechanism in the unittest to detect this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this code depends on terra because the semantics of gates won't be changed. Also CliffordUtils (but not this code) defines the num<->Clifford mapping. Updates in CliffordUtils are tested by unittest and updates in this code are tested by assertions embedded in the code. The person who updates CliffordUtils is responsible to let users know the change in num<->Clifford mapping if they do so.
if isinstance(self._interleaved_op, QuantumCircuit): | ||
interleaved_circ = self._interleaved_op | ||
elif isinstance(self._interleaved_op, Clifford): | ||
interleaved_circ = self._interleaved_op.to_circuit() | ||
elif isinstance(self._interleaved_op, Gate): | ||
interleaved_circ = QuantumCircuit(self.num_qubits, name=self._interleaved_op.name) | ||
interleaved_circ.append(self._interleaved_op, list(range(self.num_qubits))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this gate direction aware? We can instantiate experiments with different physical qubits ordering qubits=[0, 1]
and qubits=[1, 0]
, so I think this must be direction aware.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For interleaved element, it should be cared by transpile
called a few lines later. However, I have recently realized a bug related with the direction of basis gates. In BackendV2, all gates are direction aware so we need to handle all basis gates in direction-aware as you pointed out. It may be a critical bug in the era of BackendV2. I'll address the issue in a separate PR.
|
||
if isinstance(self._interleaved_op, QuantumCircuit): | ||
if not self._interleaved_op.name.startswith("Clifford"): | ||
self._interleaved_op.name = f"Clifford-{self._interleaved_op.name}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the op name does matter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I'm using it to know which operation must be decomposed within _decompose_clifford_ops
when transpiling circuits. It's a bit hacky. Should I add some comments? As discussed in #930, if we change circuits
to return unwrapped (decomposed) circuits, we don't need to worry about circuit decomposition. What do you think is good for now and future?
return circuits | ||
|
||
|
||
_CLIFFORD_LAYER = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the expected overhead of this at a time of importing this module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's almost invisible. Previously importing clifford_utils takes about 1.41--1.43 seconds and now it takes about 1.44--1.46 seconds. About 0.03 sec increase seems acceptable to me.
qiskit_experiments/library/randomized_benchmarking/clifford_utils.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/rb_experiment.py
Outdated
Show resolved
Hide resolved
MANIFEST.in
Outdated
@@ -1,3 +1,5 @@ | |||
include LICENSE.txt | |||
include requirements.txt | |||
include qiskit_experiments/VERSION.txt | |||
include qiskit_experiments/library/randomized_benchmarking/data/*.npz | |||
exclude qiskit_experiments/library/randomized_benchmarking/data/*.py |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exclude qiskit_experiments/library/randomized_benchmarking/data/*.py | |
exclude qiskit_experiments/library/randomized_benchmarking/data/generate_clifford_data.py |
I think you must explicitly state the file name. Otherwise this could cause unexpected bug in the future update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've found the simplest way to include *.npz and exclude *.py is do nothing here (and having no init file for the data directory). Resolved in a04310a
qiskit_experiments/library/randomized_benchmarking/data/generate_clifford_data.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/data/generate_clifford_data.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/data/generate_clifford_data.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/data/generate_clifford_data.py
Outdated
Show resolved
Hide resolved
releasenotes/notes/update-number-to-2q-clifford-mapping-c28f1f29b0205d57.yaml
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/interleaved_rb_experiment.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/interleaved_rb_experiment.py
Outdated
Show resolved
Hide resolved
…ity#940) * Improve integer-based Clifford operations for 1Q/2Q RB Co-authored-by: merav-aharoni <[email protected]> Including following features: * Simplify clifford_utils API * Clean up script file for clifford data generation * Change clifford data files to npz formant and add them to package * Simplify data handling for 2q clifford compose with sparse matrix * Fix a too restrictive test * Fix performance regression in transpiled 2Q Clifford circuit generation * Fix bugs in two methods for random Clifford generation in CliffordUtils * Minor improvement of interface * Fix a bug in Backend V1 to V2 conversion that caused a failure in interleaving circuit with delay using BackendV1 * Fix a bug that interleaved circuit is not always decomposed in transpiled circuit * Exclude a data generation script file from package * Simplify MANIFEST.ini * Updates following reviewers suggestions * Improve docs and a few lines of code following reviewers suggestions
…ity#940) * Improve integer-based Clifford operations for 1Q/2Q RB Co-authored-by: merav-aharoni <[email protected]> Including following features: * Simplify clifford_utils API * Clean up script file for clifford data generation * Change clifford data files to npz formant and add them to package * Simplify data handling for 2q clifford compose with sparse matrix * Fix a too restrictive test * Fix performance regression in transpiled 2Q Clifford circuit generation * Fix bugs in two methods for random Clifford generation in CliffordUtils * Minor improvement of interface * Fix a bug in Backend V1 to V2 conversion that caused a failure in interleaving circuit with delay using BackendV1 * Fix a bug that interleaved circuit is not always decomposed in transpiled circuit * Exclude a data generation script file from package * Simplify MANIFEST.ini * Updates following reviewers suggestions * Improve docs and a few lines of code following reviewers suggestions
Improve the performance of transpiled circuit generation in 1Q/2Q StandardRB/InterleavedRB (about 10x speedup in 1Q SRB/IRB and 5x speedup in 2Q SRB/IRB in most cases). Including following feature pull-requests: * New algorithm for RB building Cliffords by layers (#892) * Improve custom transpilation for faster 1Q/2Q RB (#922) * Improve integer-based Clifford operations for 1Q/2Q RB (#940) * Readd support for backends with directed basis gates to RB experiments (#960) Features other than speedup: * Fix performance regression in 3 or more qubits RB (embedded at Refactor RB module for future extensions #898) * Improve validation of interleave_element in InterleavedRB * Restructure tests for RB module * Remove the barriers in front of the first Cliffords in RB circuits Co-authored-by: merav-aharoni <[email protected]> Co-authored-by: Naoki Kanazawa <[email protected]>
Summary
This PR is a follow-up of #892, #922 for improving implementation of
clifford_utils
module.Details and comments
This PR improves the implementation of
clifford_utils
module without changing the original algorithms for integer-based Clifford operations. It includesCliffordUtils.*
to functionsclifford_utils.*
CliffordUtils
objects (ChangeCliffordUtils
back to a library class)It preserves great ideas introduced in #892 such as:
Using lookup tables for Clifford operations (
compose
andadjoint
)Composing 2Q Clifford circuit from triplets of circuit layers (that reduces computational cost for basis translation)
Release note: :meth:
.ClifforUtils.clifford_2_qubit_circuit
changes its mapping between integers and 2Q Cliffords. For a given integer, it may return a different Clifford than before.