-
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
Cleanup RB module #762
Cleanup RB module #762
Conversation
- remove metadata and backend data from curve analysis instance - update fit figure creation workflow in curve analysis
…nup/rb_remove_rawdata_dependency
005578c
to
115d4ef
Compare
gate_per_clifford[(qubits, instr)] += count / total_cliffs | ||
|
||
# Directly update analysis option | ||
self.analysis.set_options(gate_per_clifford=gate_per_clifford) |
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.
Can we upgrade base experiments so that this value can be passed to experiment data metadata? Otherwise EPGs will disappear in analysis results when we rerun analysis with retrieved experiment data.
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 is resolved by swapping the order of transpile and creation of experiment data in the base experiment.
4c69819
to
8acb0be
Compare
a48cfad
to
7785cff
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.
Initial review. This looks like a good start to cleaning up the RB analysis
qiskit_experiments/library/randomized_benchmarking/interleaved_rb_experiment.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/rb_analysis.py
Outdated
Show resolved
Hide resolved
gate_error_ratio = experiment_data.metadata.get("gate_error_ratio", None) | ||
|
||
if gate_error_ratio is None: | ||
# For backward compatibility when loading old experiment data. |
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.
Should we raise the deprecation warning here so its clearer in the code that this is a deprecated path?
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 question. As I wrote in the comment, calling RBUtils.get_error_dict_from_backend
raises deprecation warning. I agree writing explicit warning here helps maintainer, but this means user will see the warning twice.
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 think here we need to and then add the gnerated gate_error_ratio dict to the experiment data metadata so it can be re-saved to update the saved data, and then add the user/log/deprecation warning that old data was converted to new format and should be resaved to avoid it not working in the future.
qiskit_experiments/library/randomized_benchmarking/rb_experiment.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/rb_experiment.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/rb_experiment.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/rb_experiment.py
Outdated
Show resolved
Hide resolved
gate_counts_per_clifford[(qubits, instr)] += count / total_cliffs | ||
|
||
# Directly copy the value to experiment data metadata via instance state | ||
self._gate_counts_per_clifford = dict(gate_counts_per_clifford) |
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.
Same here, this should update the option value rather than a separate variable
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 value is not configurable so having it in options is bit strange. If user provides this value, they should inject already transpiled circuit and avoid transpiring.
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.
My main concern here is that we are storing a temporary variable that depends on specific circuits generated when calling circuits()
in the state of the experiment. It seems like an awkward way to get this into metadata.
If we go back to the old way of storing the op_count in circuit metadata, than this computation could be done during analysis which seems to be more where it belongs.
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.
Similar stuff also appears in CR Ham tomo experiments based on our discussion so I don't think this is awkward.
https://github.com/Qiskit/qiskit-experiments/blob/dda5ebe4438b8d0e53f2b0ddda7f6b9dab0c29f3/qiskit_experiments/library/characterization/cr_hamiltonian.py#L173-L175
Calling circuits()
doesn't populate this value. This only updated with transpile which is not exposed to users. Anyways I'll revert this logic at cost of double loop.
qiskit_experiments/library/randomized_benchmarking/rb_experiment.py
Outdated
Show resolved
Hide resolved
@@ -242,4 +301,7 @@ def _metadata(self): | |||
for run_opt in ["meas_level", "meas_return"]: | |||
if hasattr(self.run_options, run_opt): | |||
metadata[run_opt] = getattr(self.run_options, run_opt) | |||
|
|||
metadata["gate_error_ratio"] = self._gate_error_ratio | |||
metadata["gate_counts_per_clifford"] = self._gate_counts_per_clifford |
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.
metadata["gate_counts_per_clifford"] = self._gate_counts_per_clifford | |
metadata["gate_counts_per_clifford"] = self.experiment_options.gate_counts_per_clifford |
ae868fe
to
e9c66b5
Compare
if self.options.gate_error_ratio is None: | ||
gate_error_ratio = experiment_data.metadata.get("gate_error_ratio", None) | ||
|
||
try: |
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 just to handle the case of None? If so you can just do this with an else
in the next clause:
if gate_error_ration is None:
# For backward compatibility when loading old experiment data.
...
else:
gate_error_ration = dict(gate_error_ration)
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.
No, it can be False
if experiment doesn't want to perform EPG calculation.
qiskit_experiments/library/randomized_benchmarking/rb_analysis.py
Outdated
Show resolved
Hide resolved
gate_counts_per_clifford[(qubits, instr)] += count / total_cliffs | ||
|
||
# Directly copy the value to experiment data metadata via instance state | ||
self._gate_counts_per_clifford = dict(gate_counts_per_clifford) |
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.
My main concern here is that we are storing a temporary variable that depends on specific circuits generated when calling circuits()
in the state of the experiment. It seems like an awkward way to get this into metadata.
If we go back to the old way of storing the op_count in circuit metadata, than this computation could be done during analysis which seems to be more where it belongs.
qiskit_experiments/library/randomized_benchmarking/rb_experiment.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/rb_experiment.py
Outdated
Show resolved
Hide resolved
@@ -294,3 +303,84 @@ def calculate_2q_epg( | |||
out[qubit_pair] = {gate_2_qubit_type: 3 / 4 * (1 - alpha_c_2q) / n_gate_2q} | |||
|
|||
return out | |||
|
|||
|
|||
def lookup_epg_ratio(gate: str, n_qubits: int) -> Union[None, int]: |
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.
You might as well make this a static method of RBUtils
so you dont need to import two things. That said we could just remove the RBUtils class since its basically just acting as a scope for a bunch of static methods
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 think this doesn't need to be a class. We should convert these methods into functions, but this can be done in follow-up (if you want I'll do this now)
qiskit_experiments/library/randomized_benchmarking/rb_experiment.py
Outdated
Show resolved
Hide resolved
Co-authored-by: Christopher J. Wood <[email protected]>
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.
Reviewing this again I am now leaning towards making InterleaveRBAnalysis a direct subclass of CurveAnalysis. Since it seems we are doing a lot of special case handling in the options of RBAnalysis to allow for differences in its subclass, which is probably not the best way to do this.
If you have methods you want to use in both classes, you could just make them functions like you have with _lookup_epg_ratio
that can be imported for both classes, rather than using inherited methods.
qiskit_experiments/library/randomized_benchmarking/rb_analysis.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/rb_analysis.py
Outdated
Show resolved
Hide resolved
self, experiment_data: ExperimentData | ||
) -> Tuple[List[AnalysisResultData], List["pyplot.Figure"]]: | ||
|
||
if self.options.gate_error_ratio is not False: |
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'm still not thrilled about having different behavior of None vs False for this option value. It seems a lot of this complication is because of using RBAnalysis as a base class for IRB even though they don't fit super well, so I supposed it would it be better to just change InterleavedRBAnalysis to be a direct subclass of CurveAnalysis instead of RBAnalysis
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.
Done in 357f0a4
|
||
# Compare the computed EPG of the cx gate with the backend's recorded cx gate error: |
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 comparison is removed because it doesn't make sense. In IBM backend conventionally 2Q gate EPG is computed by just dividing EPC by 1.5.
c4bc7af
to
c9becae
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.
This looks much cleaner with the direct analysis subclass. I left a couple of minor suggestions.
It might be good to get someone else to proof read the RB tutorial, but any changes to that could be made into a follow up PR
gate_error_ratio (Optional[Dict[str, float]]): A dictionary with gate name keys | ||
and error ratio values used when calculating EPG from the estimated EPC. | ||
The default value will use standard gate error ratios. | ||
If set to ``False`` EPG will not be calculated. |
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 we still need the False case?
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.
Depends if you want to avoid computation of EPG. If you don't trust gate error ratio then you should be able to disable this to avoid creating some garbage in the database.
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.
In that case I think it should be None
instead of false for the skip case (since you now use "default" for computing defualt)
Co-authored-by: Christopher J. Wood <[email protected]>
…nazawa1989/qiskit-experiments into cleanup/rb_remove_rawdata_dependency
Thanks Chris. I'll merge this to move forward. Other people can update tutorial before the release if they want. |
@@ -350,6 +346,22 @@ def test_non_clifford_interleaved_element(self): | |||
lengths=lengths, | |||
) | |||
|
|||
def test_interleaving_delay(self): |
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.
You should skip this test until the 0.20.1 terra patch is released which includes the required fix there.
c51df80
to
4cd5b8f
Compare
4cd5b8f
to
6197b3c
Compare
* - remove backend and raw data dependency from RB * fix qiskit-community#727 Co-authored-by: Christopher J. Wood <[email protected]>
Summary
The computation of EPGs from EPC in analysis class introduces heavy dependency on backend and circuit metadata that causes problem for simplification of curve analysis, see #737 for details.
This mainly compute two quantities in the post-process.
gate_error_ratio
This is the ratio of errors wrt the gate kind. In principle, EPC is a single value thus we can extract only single kind of EPG from experiment. There is some trick to compute multiple values, by assuming some dependency among parameters. This is
gate_error_ratio
. In current implementation the default value isNone
and it tries to extract the ratio from backend gate properties by directly dividing gate error values, thus it introduces the backend dependency. However, this is not correct (or at least not general) approach, indeed this is based on pretty IBM-ish convention. Originally, this is from convention how we compute U1, U2, U3 error from single EPC value. Here, U1 is only VZ instruction thus error-free. U2 and U3 consist of SY and two SXs. Since SY and SX are identical physical operation (up to mixer skew), we set EPG_U2:EPG_U3 = 1:2. Same thing occurs here, i.e. now we have Rz, SX, X and EPG_Rz:EPG_SX:EPG_X = 0:1:1. Current IBM backends doesn't report exact, i.e. interleaved, EPG for SX and X thus this assumption holds. However, once it reports interleaved EPGs, EPG_SX:EPG_X != 1:1 and the ratio will change from time to time due to fitting error or amplitude nonlinearity (usually ignorable). This example indicates this approach should be removed, not only from viewpoint of cleanup, but also validity. We should always explicitly provide assumption for the gate error, and experiment guarantee the same gates are used in the transpiled circuit.gate_per_clifford
This is the averaged gate count per single Clifford gate. The gate count is stored in the circuit metadata in the form of
tuple(counts, xval)
. In the post-processgate_per_clifford
is inefficiently computed by parsing this metadata just to divide counts by total xval (i.e. total Clifford gates in per-seed experiments). Since experiment knows this xval, it can compute averaged count value before storing it in the circuit metadata. This allows us to get rid ofraw_data
dependency in the curve analysis post-processing. Indeed this is the only experiment analysis using the raw_data to access circuit metadata (in the formatted data metadata is removed because metadata cannot be averaged over the same x values).Details and comments
TODO