Skip to content
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

Update AQT Backend #6441

Merged
merged 42 commits into from
Apr 19, 2024
Merged

Conversation

jbrixon
Copy link
Collaborator

@jbrixon jbrixon commented Feb 5, 2024

Adjusted the AQT backend so that it works with the new AQT Arnica API. We also updated the gateset to reflect what we support.
As we made an extension to the API, we were able to add a feature that lists the workspaces and resources that are available to a user. We also updated tests and documentation accordingly.

Fixes #6379

Copy link

google-cla bot commented Feb 5, 2024

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@CirqBot CirqBot added the size: L 250< lines changed <1000 label Feb 5, 2024
if len(operation.qubits) == 1:
for idx in xtlk_arr.nonzero()[0]:
exponent = operation.gate.exponent # type:ignore
exponent = exponent * xtlk_arr[idx]
xtlk_op = gate.on(system_qubits[idx]) ** exponent
xtlk_op = operation.gate.on(system_qubits[idx]) ** exponent
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there needs to be a check here and on 138 to exclude the case when operation.gate is None.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Fixed.

@@ -40,16 +41,68 @@ class AQTSampler(cirq.Sampler):
runs a single circuit or an entire sweep remotely
"""

def __init__(self, remote_host: str, access_token: str):
def __init__(self, workspace: str, resource: str, access_token: str, remote_host: str = "https://arnica.aqt.eu/api/v1/"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to reorder the arguments? Is it because of the default argument?

If we keep the order the same, it would be more backwards compatible. This is doubly important since all the arguments are strings, so if people are passing them by position, they could get out of order.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it was because of the default argument. We preferred the definition of workspace and resource than requiring the user to piece together a URL, but this does require breaking backwards-compatibility. We figured that by requiring two arguments now, it would be clear enough that the "old way" is not the right way any more.

@@ -40,16 +41,68 @@ class AQTSampler(cirq.Sampler):
runs a single circuit or an entire sweep remotely
"""

def __init__(self, remote_host: str, access_token: str):
def __init__(self, workspace: str, resource: str, access_token: str, remote_host: str = "https://arnica.aqt.eu/api/v1/"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend putting "https://arnica.aqt.eu/api/v1/" into a constant at the top, such as
_DEFAULT_HOST = "https://arnica.aqt.eu/api/v1/"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agreed. Fixed.

self.remote_host = remote_host
self.access_token = access_token

@staticmethod
def fetch_resources(access_token: str, remote_host: str = "https://arnica.aqt.eu/api/v1/") -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, put remote_host default as a constant.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

raise RuntimeError('Got unexpected return data from server: \n' + str(response.json()))

workspaces = cast(list, response.json())
col_widths = [19, 21, 20, 3]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's with these random width numbers? It can't be 20, 20, 20?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are completely arbitrary and just literally the current longest possible strings. Happy to change.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should make it a constant. Or better yet, make it part of a different class rather than hardcode it in a sampler class. Also a line comment explaining that these are the current longest strings may be helpful.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was no real reason to initialise the values like that, so have adjusted this slightly.

col_widths[1] = max(col_widths[1], len(resource['name']))
col_widths[2] = max(col_widths[2], len(resource['id']))

print("+-" + col_widths[0]*"-"+ "-+-" + col_widths[1]*"-" + "-+-" + col_widths[2]*"-" + "-+-" + col_widths[3]*"-" + "-+")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend creating a data structure here. Perhaps a dataclass or attrs that has workspace_id, resource_name, resource_id as member variables. Then, the function can return that. This will be more programmatically useful.

Optionally, you could then have print_resources somewhere that prints this out in this fixed width format.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We considered this, but we currently need the user to manually choose the workspace and resource they want to use. By allowing these to be chosen programmatically somehow, it could introduce errors that might lead to confusion. There is also limited information currently provided by the API with which one would programmatically choose - it is literally just a list of acceptable workspace/resource IDs.

This is intended purely as a helper function to allow users to look up the ID of resources that they already have access to, in case they forgot to write them down or something.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't think this is good design. Having a Workspace class that contains names and ids of resources would be a natural way to encapsulate this information returned from the API, provide a good place to document what is returned, be more testable, and be more testable. You could then have a print() (or just put it in str) function on this class to print the information as it is here. It also would not take too much work to move this into a class.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I certainly agree it's not good design. Have adapted slightly to expose a fetch as well as a print static method.

Used TypedDict instead of classes to stay consistent with what was already there. Note sure in hindsight that that was the best idea.

def _parse_legacy_circuit_json(self, json_str: str) -> list:
"""Converts a legacy JSON circuit representation.

Converts a JSON created for the legacy API into one that will work
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these APIs have versions? "Converts a JSON created for the API v0.3 ..." would be more precise and less prone to misinterpretation when the newer than new API comes out next year (or whenever)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, now they do. Updated. Thanks!

@@ -99,28 +152,78 @@ def _generate_json(
raise RuntimeError('Cannot send an empty circuit')
json_str = json.dumps(seq_list)
return json_str

def _parse_legacy_circuit_json(self, json_str: str) -> list:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better typing would be "-> list[dict[str,str]]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not quite so simple, but I've improved this return type anyway with better definitions for the operations.

@@ -223,10 +341,12 @@ class AQTSamplerLocalSimulator(AQTSampler):
sampler.simulate_ideal=True
"""

def __init__(self, remote_host: str = '', access_token: str = '', simulate_ideal: bool = False):
def __init__(self, workspace: str = "", resource: str = "", access_token: str = "", remote_host: str = "", simulate_ideal: bool = False):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, it would be nice to preserve backwards compatibility.

@dstrain115
Copy link
Collaborator

Also, you can fix the formatting by running check/format --apply

Copy link
Collaborator

@dstrain115 dstrain115 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a few last nits. Thanks for addressing comments.

@@ -36,11 +37,18 @@
gate_dict = {'X': cirq.X, 'Y': cirq.Y, 'Z': cirq.Z, 'MS': cirq.XX, 'R': cirq.PhasedXPowGate}


class OperationString(Enum):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a short docstring for this enum.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - fixed.

'Y': cirq.depolarize(1e-3),
'Z': cirq.depolarize(1e-3),
'R': cirq.depolarize(1e-3),
'Z': cirq.depolarize(0),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use the enum values?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Fixed. Thanks

@dstrain115
Copy link
Collaborator

dstrain115 commented Mar 29, 2024

It looks like there are some integration test failures. Once these are resolved, this should be good to merge!
Thanks for addressing all the requested changes!

@dstrain115
Copy link
Collaborator

Sent you an invite to be a collaborator, which should allow you to run CI on your own.

@jbrixon
Copy link
Collaborator Author

jbrixon commented Apr 15, 2024

Sent you an invite to be a collaborator, which should allow you to run CI on your own.

Thanks. Have a weird mismatch between formatting locally and on CI. Was missing some test coverage too, but let's pretend that didn't happen..! Looks like everything's passing now finally...

Copy link

codecov bot commented Apr 15, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.79%. Comparing base (21986dd) to head (dab7e05).

❗ Current head dab7e05 differs from pull request most recent head fd6e8c9. Consider uploading reports for the commit fd6e8c9 to get more accurate results

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #6441    +/-   ##
========================================
  Coverage   97.78%   97.79%            
========================================
  Files        1124     1124            
  Lines       95493    95680   +187     
========================================
+ Hits        93380    93570   +190     
+ Misses       2113     2110     -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@dstrain115 dstrain115 enabled auto-merge (squash) April 16, 2024 18:35
@dstrain115 dstrain115 added the automerge Tells CirqBot to sync and merge this PR. (If it's running.) label Apr 16, 2024
@dstrain115 dstrain115 merged commit e1b03ef into quantumlib:main Apr 19, 2024
29 checks passed
jselig-rigetti pushed a commit to jselig-rigetti/Cirq that referenced this pull request May 28, 2024
Adjusted the AQT backend so that it works with the new AQT Arnica API. We also updated the gateset to reflect what we support.
As we made an extension to the API, we were able to add a feature that lists the workspaces and resources that are available to a user. We also updated tests and documentation accordingly.

Fixes quantumlib#6379
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge Tells CirqBot to sync and merge this PR. (If it's running.) size: L 250< lines changed <1000
Projects
None yet
Development

Successfully merging this pull request may close these issues.

cirq-aqt module not working anymore (due to breaking changes in the AQT API)
4 participants