-
Notifications
You must be signed in to change notification settings - Fork 128
Clifford Mirror RB experiment #1090
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
base: main
Are you sure you want to change the base?
Conversation
…ircuits, comment pauli label
the default integer method wasn't working with the later `Clifford()` invocation.
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 Helena. This version looks much cleaner than old one. Probably I added too much comments.
qiskit_experiments/library/randomized_benchmarking/sampling_utils.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/randomized_benchmarking/mirror_rb_analysis.py
Outdated
Show resolved
Hide resolved
|
||
# Calculate EPG | ||
if self._gate_counts_per_clifford is not None and self.options.gate_error_ratio: | ||
epg_dict = _calculate_epg( |
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 apply the same logic to MRB alpha?
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 this comment doesn't apply any more now that the analysis class is subclassing from 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.
Nope. Option and _create_analysis_results
are still inherited from RBAnalysis and an algorithm to exclude 1q contribution is still applied here. I mathematically validated this exclusion for standard RB case, but I'm not sure if it's still valid for MRB alpha. This mechanism should be validated otherwise you should drop these options.
qiskit_experiments/library/randomized_benchmarking/mirror_rb_analysis.py
Outdated
Show resolved
Hide resolved
test/library/randomized_benchmarking/test_randomized_benchmarking.py
Outdated
Show resolved
Hide resolved
|
||
|
||
@ddt | ||
class TestRunMirrorRB(RBRunTestCase): |
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 you don't have a test for other y_axis? Sorry if I overlooked.
now `sampling_algorithm` specifies the algorithm in str form and `sampler_opts` is passed at sampler instantiation. two-qubit gate and density are only passed to the default edge grab sampler, otherwise users have to implement their own methods. also addressed some review comments.
also updated test backends to V2.
Co-authored-by: Naoki Kanazawa <[email protected]>
qiskit_experiments/library/randomized_benchmarking/mirror_rb_experiment.py
Show resolved
Hide resolved
and added validation for analysis option
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.
Thank you for the updates, @coruscating . Now it looks better to me but I think we can improve the API of Samplers a bit more.
IMO, MirrorRB
class should not be aware of GateDistribution
, which should be a helper class to implement Samplers. And I think it's too demanding for users to ask to implement _set_distribution_options
(i.e. subclass MirrorRB class) for all samplers other than EdgeGrabSampler. I also think BaseSampler
should have no implementation, so it should have only __init__
and __call__
.
The psuede code in my mind looks like (updated March 30):
# in MirrorRB.__init__
sampler_opts.update({"backend": backend, "physical_qubits": physical_qubits, "seed": seed})
self._distribution = self.sampler_map.get(sampling_algorithm)(**sampler_opts)
# BaseSampler
def __init__():
# do nothing
# EdgeGrabSampler
def __init__(seed=None, physical_qubits=None, backend=None, reduced_coupling_map=None, **sampler_opts):
# Move logics currently in MirrorRB._set_distribution_options here, e.g.
if backend:
coupling_map = CouplingMap(backend.coupling_map)
else:
coupling_map = CouplingMap.from_full(len(physical_qubits))
self._coupling_map = coupling_map.reduce(physical_qubits)
...
# SingleQubitSampler
def __init__(seed=None, gate_distribution=None, **sampler_opts):
Also why don't we have Pauli1QSampler
and Clifford1QSampler
, which are dedicated samplers for 1Q Paulis and 1Q Cliffords? If we have those, we don't need to introduce GenericClifford
, GenericPauli
and SimpleQubitSample._probs_by_gate_size
.
qiskit_experiments/library/randomized_benchmarking/mirror_rb_experiment.py
Outdated
Show resolved
Hide resolved
pauli_sampler = SingleQubitSampler(seed=self.experiment_options.seed) | ||
pauli_sampler.gate_distribution = [GateDistribution(prob=1, op=GenericPauli(1))] | ||
|
||
if self.experiment_options.start_end_clifford: | ||
clifford_sampler = SingleQubitSampler(seed=self.experiment_options.seed) | ||
clifford_sampler.gate_distribution = [GateDistribution(prob=1, op=GenericClifford(1))] |
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 don't we have Pauli1QSampler
and Clifford1QSampler
, which are dedicated samplers for 1Q Paulis and 1Q Cliffords? If we have those, we can simply write pauli_sampler=Pauli1QSampler(seed=self.experiment_options.seed)
here and readers don't need to understand the spec of GateDistribution
and GenericPauli
.
|
||
""" | ||
|
||
sampler_map = {"edge_grab": EdgeGrabSampler, "single_qubit": SingleQubitSampler} |
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 feel having this and introducing sampler_opts
is a good idea.
try: | ||
self._coupling_map = CouplingMap(coupling_map) | ||
except (ValueError, TypeError) as exc: | ||
raise TypeError("Invalid coupling map provided.") from exc |
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.
💯
inverting_pauli_layer=inverting_pauli_layer, | ||
) | ||
|
||
self._distribution = self.sampler_map.get(sampling_algorithm)(seed=seed, **sampler_opts) |
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 need to catch key error with friendly message. get
without default value is just directly accessing the attribute.
physical_qubits: Sequence[int], | ||
lengths: Iterable[int], | ||
distribution: BaseSampler = EdgeGrabSampler, | ||
start_end_clifford: bool = True, |
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.
Fair enough. Let's do in follow-up.
target_bs.append(circ_result["metadata"]["target"]) | ||
|
||
self.set_options( | ||
data_processor=DataProcessor( |
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.
Cool. It looks clean now 💯
) | ||
|
||
|
||
class ComputeQuantities(DataAction): |
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.
See #1090 (comment)
|
||
# Calculate EPG | ||
if self._gate_counts_per_clifford is not None and self.options.gate_error_ratio: | ||
epg_dict = _calculate_epg( |
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.
Nope. Option and _create_analysis_results
are still inherited from RBAnalysis and an algorithm to exclude 1q contribution is still applied here. I mathematically validated this exclusion for standard RB case, but I'm not sure if it's still valid for MRB alpha. This mechanism should be validated otherwise you should drop these options.
@itoko Because we don't require the user to provide a backend on experiment instantiation, the sampler class was designed to have a minimal |
- protect data processor node - moved edge grab distribution validation to setter - fixed tests and added invalid distribution tests
Ah, I missed the point ( |
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 for the update. Now analysis class looks much better. Just minor comment and I'll delegate approval to @itoko
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.
Aside from requiring sampler's options at initialization or allowing their replacement layer, my point is that users (not MirrorRB class) should handle Sampler's construction completely. (Note that Sampler's interface is very important because the optionality of benchmarking circuits is one of the selling points of Mirror RB.)
For that, a naive interface would look like MirrorRB(sampler=MySampler(a, b, c, x, y, z), x=x, y=y, z=z))
but we don't want to ask users to set the duplicated options (x, y, z) twice. So current interface like MirrorRB(sampler_algorithm="my_sampler", sampler_opts={a: a, b: b, c: c}, x=x, y=y, z=z)
(or MirrorRB(sampler=MySampler, sampler_opts={a: a, b: b, c: c}, x=x, y=y, z=z)
) looks good to me. I recognize that is a good "syntax sugar" of the former. That means, in principle, users need to set full options for Sampler by themselves (via sampler_opts
) but they don't need to specify options duplicated with arguments of MirrorRB initializer exceptionally. Consequently, I think MirrorRB
class should not do anything more than sampler_opts.update({mirror_rb_initializer_args})
and sampler_map[sampler_algorithm](**sampler_opts)
, and all objects necessary for a Sampler (e.g. reduced coupling map) should be provided via sampler_opts
or created within the Sampler (not in MirrorRB). (See also the sample code in the previous comment I updated.) What do you think? @coruscating
I think current code is heading towards the direction and almost there but some codes in MirrorRB seem still do what should be done in Sampler, i.e. _set_distribution_options
.
Added later:
Alternative design of MirrorRB initializer might be having sampler: Optional[BaseSampler] = None
, building EdgeGrabSampler
in the case of None and allowing to reset sampler
via experiment.sampler = MySampler(...)
. In this case, we don't need to have sampler_map
and sampler_opts
. Concerns on serialization make difficult to take this simpler design?
Anyway, if a user wants to reset backend
or any experiment options which are used for building a sampler, it should be the user's responsibility to reset sampler
at the same time. I think it's not demanding since it can be usually avoided by replacing
exp = Experiment()
for config in configs:
exp.set_experiment_options(config=config)
exp.run(...)
with
for config in configs:
exp = Experiment(config=config)
exp.run(...)
_self_adjoint_gates = [CXGate, CYGate, CZGate, ECRGate, SwapGate, iSwapGate] | ||
|
||
|
||
class MirrorRB(StandardRB): |
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.
Any thoughts on this point @coruscating ?
def _set_distribution_options(self): | ||
"""Set the coupling map and gate distribution of the sampler | ||
based on experiment options. This method is currently implemented | ||
for the default "edge_grab" sampler.""" |
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 suggest to replace this method with just a getter and a setter for distribution
(any better name? layer_sampler
?) and move all logics in this method under EdgeGrabSampler
. (as I mentioned the reason in the main comment)
@itoko I think Another issue is if we have I like the alternative design, and I think it will be clear to the user that they have to reset |
Thank you for providing detailed issues.
My preferable API at this moment looks like this. |
Thanks for writing up the API! Regarding 1., that is a nice solution, will do. I don't think Regarding 2., Without |
I don't think of any use case for X% of one gate and Y% of another so far. Also I'm not sure if users (including I) can understand correctly what happens if they supply |
One example could be the universal mirror RB with SU2 gates protocol: the paper uses { |
Oh, I missed that case, but just for covering the case, we could extend |
Summary
Implements mirror RB with single Clifford + CX layers and a sampler class for generating layer samples.
Details and comments
This is the updated version of #842 that has been refactored in the new RB structure. There were problems with the reno history that I couldn't fix without rebasing, and I didn't want to overwrite the branch history so I continued on my fork.
The main changes from that PR are adding a Sampler class based on @ihincks's comment and refactoring mirror RB into the new RB structure, keeping the single qubit Cliffords integers until circuit generation. I tested with a two-qubit experiment and it was roughly ~20% faster than the previous implementation.
Also, for computing the target bitstrings,
Clifford()
is used but it doesn't work when the basis gates are specified, so currently two copies of the circuits are made, one being the actual circuits and one without basis gates for calculating the target bitstring. Once Qiskit/qiskit#9475 is merged, the redundant circuit generation step can be removed (thanks to @itoko for helping with this).