-
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
Rework of Stark module and workflow #1236
Rework of Stark module and workflow #1236
Conversation
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 good. Besides some wording suggestions, I wanted to know what you think about having a more standardized location for the frequency shift polynomial coefficients. Other than that I think this is ready to merge.
qiskit_experiments/library/characterization/analysis/t1_analysis.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/characterization/analysis/t1_analysis.py
Outdated
Show resolved
Hide resolved
4c2f37c
to
98c7e0d
Compare
Co-authored-by: Will Shanks <[email protected]>
092a874
to
6e2f441
Compare
…ure/stark_p1_frequency_scan
@wshanks I did major rework of this branch in da444b6 and updated the PR title to align with what it is doing. This is based on what you suggested in the comment, namely, creating new 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.
This is great. I have one practical issue with retrieving the coefficients from the live service (not sure if we could test that case better here?) and then some questions/suggestions.
qiskit_experiments/library/driven_freq_tuning/coefficient_utils.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/driven_freq_tuning/coefficient_utils.py
Outdated
Show resolved
Hide resolved
qiskit_experiments/library/driven_freq_tuning/coefficient_utils.py
Outdated
Show resolved
Hide resolved
valid_amp = float(valid_amps[before_inflection].real) | ||
else: | ||
valid_amp = float(valid_amps[0].real) | ||
amplitudes[idx] = min(valid_amp, 1.0) |
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.
Couldn't you avoid the min(..., 1.0)
part if you did not allow the amp to be 10 * np.finfo(float).eps
more than 1.0 above?
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 coded like you mentioned in the beginning, but found that some test case (round trip case for amplitude input = 1.0) fails due to numerical precision problem. This is the trick to allow full dynamic range more safely.
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.
Okay, I see. So you do a sweep up to amp=1.0 and get a set of coefficients. Then you try to sweep up to the max frequency and get an error because rounding causes the max frequency to correspond to an amp slightly above 1 and be filtered out above.
Maybe a comment would be good: "This clipping is okay because values well above 1.0 were filtered out above". I would consider move this operation just below the filtering so it stays close.
By the way, should it be: np.sign(valid_amp) * min(abs(valid_amp), 1.0)
?
retrieved = service.analysis_results( | ||
device_components=[f"Q{qubit}"], | ||
result_type="stark_coefficients", | ||
backend_name=backend_name, | ||
sort_by=["creation_datetime:desc"], | ||
) |
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.
Something is off here for me. IBMExperimentService
does not know about the StarkCoefficients
class so it should not be able to return an instance of it from analysis_results()
here like the type hint suggests. I tried adding json_decoder=ExperimentDecoder
, but that does not completely solve the issue. When I do that, I get a retrieved[0].result_data
like:
{'value': '(StarkCoefficients)',
'_chisq': 8.026329774009517,
'_extra': {'run_time': None, 'experiment': 'StarkRamseyXYAmpScan'},
'_value': StarkCoefficients(pos_coef_o1=5748041.745393166, pos_coef_o2=17930671.766640767, pos_coef_o3=7365555.846512856, neg_coef_o1=-581564.1840185896, neg_coef_o2=-27118377.31311793, neg_coef_o3=3073801.6774436794, offset=-658782.8378606051),
'_source': {'class': 'qiskit_experiments.framework.analysis_result.AnalysisResult',
'data_version': 1,
'qiskit_version': {'qiskit': '0.45.1', 'qiskit-experiments': '0.6.0'}}}
So it does have the StarkCoefficients
instance in _value
but it is a dict and 'value'
is just a string (StarkCoefficients)
.
If I don't set the decoder I get
{'value': '(StarkCoefficients)',
'_chisq': 8.026329774009517,
'_extra': {'run_time': None, 'experiment': 'StarkRamseyXYAmpScan'},
'_value': {'__type__': 'object',
'__value__': {'class': {'__type__': 'type',
'__value__': {'name': 'StarkCoefficients',
'module': 'qiskit_experiments.library.driven_freq_tuning.coefficient_utils',
'version': '0.6.0.dev0+da444b6'}},
'version': '0.6.0.dev0+da444b6',
'settings': {'offset': -658782.8378606051,
'neg_coef_o1': -581564.1840185896,
'neg_coef_o2': -27118377.31311793,
'neg_coef_o3': 3073801.6774436794,
'pos_coef_o1': 5748041.745393166,
'pos_coef_o2': 17930671.766640767,
'pos_coef_o3': 7365555.846512856}}},
'_source': {'class': 'qiskit_experiments.framework.analysis_result.AnalysisResult',
'data_version': 1,
'qiskit_version': {'qiskit': '0.45.1', 'qiskit-experiments': '0.6.0'}}}
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.
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.
fyi #1383
), | ||
) | ||
np.testing.assert_array_almost_equal(test_xvals, ref_xvals) | ||
analysis.run(exp_data, replace_results=True).block_for_results() |
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 doesn't matter, but ExperimentData.analysis_results()
blocks by default so you don't need to block here. At one point, I started going through the tests adding block_for_results
before realizing this because I was worried about race conditions.
|
||
|
||
@dataclass | ||
class StarkCoefficients: |
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 like how this class cleans up the experiment classes. All the individual functions in this module either take StarkCoefficients
as an argument or create a StarkCoefficients
instance. Maybe they would be better as methods and classmethod
alternate constructors on the class instead of separate functions? Then you could call this file just coefficients.py
. Whenever a file has util
in the name, it tends to attract unrelated stuff.
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 suggestion makes perfect sense. I did some refactoring in 7675cbb. Note that I decided to keep two functions for data retrieval separated to decouple the service API from the local object. I think this structure should be fine.
) | ||
|
||
|
||
class StarkRamseyXYAmpScanAnalysis(curve.CurveAnalysis): |
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 reason to put the two classes together here when we usually use separate files? The corresponding experiments use separate files for example. If we want to group classes together more, I think it would make more sense to group the analysis classes with the experiments (StarkRamseyXYAmpScanAnalysis
in ramsey_amp_scan.py
for example).
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, I just hesitate to have lengthly file name *_analysis.py
. But split the file into two according to our convention.
Co-authored-by: Will Shanks <[email protected]>
- Convert util functions to class methods. Change file name to restrict the context. - Split analysis file
98f3f34
to
12ee094
Compare
12ee094
to
3e9a576
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.
Some minor comments, look good to me!
RuntimeError: When stark_coefficients entry doesn't exist in the service. | ||
""" | ||
try: | ||
if isinstance(qubit, (list, tuple)) and len(qubit) == 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 is this needed, rather than just accepting an int? The type hint should be probably be int | list[int] | tuple[int]
. Should it error if the length is not 1? Just fail to find Q[0, 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.
Good point. Indeed this input violates the type hint. The type cast is removed in 8ba6fe4. I'm used to writing a qubit index with single element tuple, so this was mainly to cope with my habit.
from qiskit_experiments.framework.experiment_data import ExperimentData | ||
|
||
|
||
class StarkCoefficients: |
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.
Just curious, why did you switch away from a dataclass?
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.
When is_dataclass(my_object) == True
I think the object is a simple data container, however, with the refactoring the StarkCoefficients
gained several methods that perform more than data formatting. To avoid implementing something that doesn't compatible with dataclass, I decided to convert this into a subclass of bare python object.
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 see. That is reasonable. I think there are different perceptions of dataclass
in the Python community. Some see it as a simple data container like you mention. Others see it as a helper that removes the boilerplate of writing a self.x = x
init method and writing repr and equality methods from otherwise normal classes.
try: | ||
if isinstance(qubit, (list, tuple)) and len(qubit) == 1: | ||
qubit = qubit[0] | ||
retrieved = service.analysis_results( |
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 this put limit=1
? I noticed that the qiskit-ibm-experiment has a default limit of 10 because when I first tried to test the new code I kept getting errors about coefficient_util
not existing. I realized that this was from results from my previous testing still being in the most recent 10 results that were retrieved.
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 finding this. Likely this is an edge case but worth adding the limit. Done in aaee397
Co-authored-by: Will Shanks <[email protected]>
Thanks @wshanks for careful review and testing. I think now the code is ready. |
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.
Looks good!
backend_name=backend_name, | ||
sort_by=["creation_datetime:desc"], | ||
json_decoder=ExperimentDecoder, | ||
# Returns the latest value only. IBM service returns 10 entries by default. |
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 comment is necessary. We only want one value so limiting to 1 is reasonable. It should be more efficient, though probably negligibly. The old data that could not be deserialized was just what made me realize in testing that we were retrieving more than one result here.
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, 1 is reasonable mainly for efficiency but I think it's worth having some note about edge case (so that someone save time for debug)
Summary
This PR moves all Stark experiments to new module
qiskit_experiments.library.driven_freq_tuning
to define a module-wise utility file. This util file contains theStarkCoefficients
dataclass to combine all seven coefficients from third-order polynomial fit characterizing the Stark shift. This object is shared among all experiments and analyses in new module.In addition to this,
StarkP1Spectroscopy
allows users to scan xval in units of either amplitude or frequency (previously only amplitude was allowed). These two domains are mutually convertible with theStarkCoefficients
object. The domain conversion functions are also included in the util file.Details and comments
Experiment option names are updated to be more general, namely
amp
->xval
and new optionxval_type
is added.xval_type
is eitheramplitude
orfrequency
. Experimentalist can directly specify the target Stark shift byNote that this requires pre-calibration of Stark shift coefficients with
StarkRamseyXYAmpScan
experiment to convert specified frequencies into tone amplitudes, and one must save the calibration results in the experiment service. If the service is not available, one can also directly provide these coefficients instead of providing a service through the backend.When the coefficients are already calibrated, one can estimate the maximum Stark shift available within the power budget.