Connecting to quantum services with Tangelo

Tangelo provides convenient ways to help you run experiments on quantum hardware. The tangelo.linq module integrates the API provided by some hardware providers (IonQ, Honeywell…) or broader quantum cloud services providers such as Azure Quantum (Microsoft) or Braket (AWS). Here’s what Tangelo provides to support you:

Regardless of what you pick: you are responsible for configuring the environment allowing you to submit jobs, and entering your own credentials. You are billed directly by the target quantum cloud service(s), Tangelo is simply here as a helpful, transparent middleman.

This approach is an alternative to using QEMIST Cloud, which we intend to make a single and convenient entry point to reach many different platforms. What’s pretty cool with this other option is that users do not have to set up the environment required by the quantum cloud(s) of their choice or even an account with those services. A QEMIST Cloud account provides a single entry point enabling you to reach all of them, and pay with your QEMIST Cloud credits.

Table of contents


Requirements

  • Tangelo needs to be installed in your environment. The cell below does this installation for you if it is not found.
  • The requirements specific to the target quantum services are detailed below, in the corresponding section. It’s usually about setting up your environment and obtaining valid credentials.
# Installation of tangelo if not already installed.
try:
    import tangelo
except ModuleNotFoundError:
    !pip install git+https://github.com/goodchemistryco/Tangelo.git@develop  --quiet

Method 1: Fine control (Translation functions)

In order to submit an experiment through some quantum services, you need to provide objects such as quantum circuits or qubit operators in one of the formats they support. For example, IBM may support formats such as OpenQASM and Qiskit, Amazon supports Braket, and your favorite hardware provider may support their own specific format using native gates not supported anywhere else.

Tangelo expresses quantum circuits and operators in its own generic format, in order to keep the library compatible with various technologies, and ready to accomodate the quantum platforms of tomorrow. The linq module contains a collection of translation functions to convert from the Tangelo format to popular formats (and sometimes the other way around). If you haven’t, check out this notebook for examples.

Here is a simple example with IonQ, but there are various different formats supported in Tangelo. IonQ’s hardware is available through several cloud services, but they also have their own cloud portal. The following shows how to translate a Tangelo circuit into the json IonQ format detailed in their documentation.

from tangelo.linq import Circuit, Gate, translate_circuit

circ1 = Circuit([Gate("H", 0), Gate("X", 1)])
json_circ1 = translate_circuit(circ1, target="ionq")
print(json_circ1)
{'qubits': 2, 'circuit': [{'gate': 'h', 'targets': [0]}, {'gate': 'x', 'targets': [1]}]}

You now have a quantum circuit in a very specific format, supported by IonQ’s platforms. You can interact directly with their cloud API, and are in full control of all the steps: connecting to their quantum services, submitting and managing quantum jobs, etc.

  • Pros: Fine control, can leverage latest features / changes from a hardware provider.

  • Cons: You need to learn how to interact with the target platform (e.g their API), and are responsible for everything from there on to run the hardware experiment.


Method 2: Convenience (Connection classes)

Connection classes are here for convenience, to provide a simple, more generic way to connect to various platforms and manage experiments programmatically. We recommend that you do not hesitate to leverage whatever graphic interface / dashboard the target quantum service provider offers, as it is usually a more comfortable way to access and visualize a lot of information about the systems available or your jobs.

The connection classes spare you a number of lines of code and having to figure out a bunch of APIs, but you still have to install whatever your environment needs and provide your own valid credentials to the target services. Not all quantum services offer the same functionalities, or handle authentication similarly: we recommend you have a look at the classes themselves (the develop branch may have additional features) to see what they have to offer or how they accomplish it.

Here’s how it typically works:

Step Description
Pre-requisites Install the required packages, acquire valid credentials for the target quantum service (check out their documentation)
Create connection objects Instantiate relevant connection object (e.g IBMConnection, BraketConnection, …). This usually requires you to provide your credentials either as parameters or through environment variables.
Submit a job Use the job_submit method. It takes your Tangelo objects as input and some options. It returns and logs job IDs
Manage your job Use job_status or job_cancel methods on jobs you’ve already submitted, through their job ID.
Retrieve your results Use the job_results method to retrieve your job results in a generic Tangelo format (bitstrings with qubit 0 on the left, read left-to-right), but also in raw format with all the metadata provided by the target service.

Depending on the service, additional methods and features may be available (listing all devices visible on the service, obtaining information about their noise, cost estimate …). Let’s see a few examples.

1. BraketConnection example

1.1 Pre-requisites

Install the Braket python SDK provided by Amazon (pip install amazon-braket-sdk). You also need to install the AWS CLI, and assumes you have an IAM user with proper permissions (Braket, S3 buckets). The Braket services can also be accessed through your web browser, which gives you access to managed python notebooks. These actions may require assistance from whoever manages cloud access in your organization.

Before you are able to use the service, some environment variables should be set. Depending on how you use Braket (managed notebooks, etc), you may or may have not to set all of these variables yourself. The following should be enough, replace the dots by the appropriate strings. Some of these information may be in your ~/.aws/credentials file.

# Set up credentials [clear before pushing to public repo]
os.environ["AWS_REGION"] = "..."
os.environ["AWS_ACCESS_KEY_ID"] = "..."
os.environ["AWS_SECRET_ACCESS_KEY"] = "..."
os.environ["AWS_SESSION_TOKEN"] = "..."

In order to submit experiments through Braket or retrieve results, you need to provide the name of a S3 bucket your account has access to, as well as a folder for the results. We recommend you ensure that is set up before proceeding.

1.2 Instantiate the connection object

As long as the Braket python package is found in your environment, instantiation will be successful. Your environment variables or S3 bucket / folder come into play later, as you attempt to make requests to the service. You can set up your S3 bucket name and folder during instantiation (or change their value manually later using the s3_bucket and folder attributes).

from tangelo.linq.qpu_connection.braket_connection import BraketConnection

conn = BraketConnection(s3_bucket="your_bucket_name", folder="your_folder_name")

1.3 Submit and manage jobs, additional features.

The fastest way to see things in action may be to check out our tests. It’s pretty straightforward.

This connection object supports batch submission of quantum circuits, which means you could provide a list of circuits and submit them all at once, otherwise your jobs may not run in similar conditions (quantum devices exhibit noise drift and are recalibrated regularly). To see all what the interface has to offer, the best way is to check out the implementation.

2. IBMConnection example

We have run an experiment on IBM devices for the IBM Quantum Summit of 2022, and it is probably the easiest way to see it in action. Section 4 of this notebook details the submission of the experiment itself using IBM Quantum, which involves Zero-Noise Extrapolation and plotting with error bars. You can find our implementation details here.

The gist of it:

  • you need to install qiskit and qiskit-runtime, and provide your IBM_TOKEN before instantiating the connection.
  • the interface is generic (job_submit, job_cancel, job_results, …). job_submit can use the sampler or estimator services, which respectively return histograms of quasi-frequencies, or expectation values.
  • the interface supports a number of options for batch submission, optimization and noise-mitigation.

It’s pretty easy to create an account on IBM Quantum, and even get access to some quantum computers for free.

3. IonQConnection example

3.1 Pre-requisites

IonQ does not require any package in particular to be installed. As long as you can interact with their REST API, all is well. At the moment of writing, users allowed to use the IonQ API should have an ID token, e.g a string of alphanumeric characters and dashes, which can be obtained through the IonQ dashboard. Users need to set their IONQ_APIKEY environment variable to this value; here are two ways to do it:

  • in your terminal (export IONQ_APIKEY=<value>)
  • or, in your Python script (os.environ['IONQ_APIKEY'] = <value>)

Here’s an example of what it could look like for you (make sure you use a valid key, or you’ll get an “unauthorized” error :) )

import os
os.environ["IONQ_APIKEY"] = '2T14z1YQEzMLCwuYM110oXPDT2h850E4'

3.2 Instantiate the connection object

The IonQConnection class encapsulates a collection of wrappers to the IonQ REST API. Internally, it stores information about the endpoint and the authorization header, containing your identification token. This class only is instantiated succesfully if your ID token has been set properly, otherwise you’ll get an error.

More generally speaking, all calls to the REST API are checked for errors, and would return the IonQ error message corresponding to the unsuccessful request.

from tangelo.linq.qpu_connection import IonQConnection

ionq_connection = IonQConnection()

3.3 Submit and manage jobs, and additional features

Here we detail a number of features available using the IonQ portal.

Backend info

The get_backend_info method returns some information about all the devices listed on IonQ’s platform. Tangelo currently returns this info inside a pandas.DataFrame object, to help with visualization.

res = ionq_connection.get_backend_info()
res # to display the dataframe neatly in this notebook
backend qubits status last updated average queue time characterization_url
0 qpu.harmony 11 available 2022-07-29 06:16:04 735 None
1 simulator 19 available 2022-07-29 06:16:04 0 None
2 qpu.s11 11 available 2022-07-29 06:16:04 735 /characterizations/e8d7ac98-b7c2-443c-9eb8-105...

This information can help users to filter or sort devices according to their needs. For example, filtering out devices who do not have enough qubits for the target experiment, as well as the unavailable devices.

We can also retrieve “characterizations”: a snapshot of the IonQ platform’s performance at a moment in time. We can use the get_characterization method with either a backend string (ex: qpu.harmony or simulator) or a characterization url, if available (see dataframe above).

charac_dict = ionq_connection.get_characterization('qpu.s11')
print(charac_dict['fidelity'])
print(charac_dict['timing'])
{'1q': {'mean': 0.9979}, '2q': {'mean': 0.961}, 'spam': {'mean': 0.99752}}
{'t1': 10000, 't2': 0.2, '1q': 1e-05, '2q': 0.0002, 'readout': 0.00013, 'reset': 2e-05}

This information can help users having a better understanding of the capabilities of a device, and anticipate its performance on their usecases. Please check IonQ’s documentation to confirm what these quantities mean, and the units in which they are expressed in.

Job submission

Submit a job with the job_submit method. It returns a job ID if submission was successful.

At the time of writing, this method takes input arguments that need to be provided by the user:

  • the target backend (strings provided by IonQ to refer to their simulators or QPUs)
  • the quantum circuit (in Tangelo format)
  • the number of shots required
  • a name for your job
  • any other option as key arguments (see source code and IonQ documentation)

Assuming a valid API key, we here submit a simple job targeting their statevector simulator. The status of the job may be in various states (queued, ready, running …).

job_id = ionq_connection.job_submit('simulator', circ1, 100, 'test_json_job')
Job submission  ID :: dfb38687-8c70-4b40-833d-c6166a902efe   status :: ready

Job history and job info

Users can access their job history and info through the job_get_history and job_status methods, shown as below. But IonQ also provides an online dashboard, which may be more convenient to you.

Depending on the timing of your REST requests, the job info may differ widely, from a failed job to a completed job with results included.

job_status = ionq_connection.job_status(job_id)
print(job_status)
Job info    ID:: dfb38687-8c70-4b40-833d-c6166a902efe    status :: completed 
{'status': 'completed', 'name': 'test_json_job', 'target': 'simulator', 'predicted_execution_time': 4, 'execution_time': 19, 'id': 'dfb38687-8c70-4b40-833d-c6166a902efe', 'qubits': 2, 'type': 'circuit', 'request': 1659075527, 'start': 1659075529, 'response': 1659075529, 'gate_counts': {'1q': 2}, 'data': {'histogram': {'2': 0.5, '3': 0.5}, 'registers': None}}

The output of job_get_history should at least feature the job we just submitted, and can also show a number of previous jobs run under your account. The output is a pandas dataframe, in order to facilitate parsing of information:

job_history = ionq_connection.job_get_history()
job_history[:5] # Here we only display info of the last 5 jobs run
id status target
0 dfb38687-8c70-4b40-833d-c6166a902efe completed simulator
1 b486e66a-2ae2-4275-8c9e-015a751dcbbe completed simulator
2 5dea41e0-ce02-4193-8e50-e48fc0cd0c22 canceled qpu.harmony
3 34cc2763-f617-427a-8b3a-621086946a3b completed qpu.harmony
4 777237b4-79e8-44d0-8ac6-762f24864e7a completed simulator

Job results

The method job_results provides a wrapper to a blocking call, querying for the state of the target job at regular intervals, attempting to retrieve the results. If the job has successfully completed, this method returns a dictionary with bitstring keys (ex: 01, 11…)

IonQ raw results use a “most-significant qubit first” representation, encoded as an integer, but Tangelo returns them as a bitstring in least-significant order (e.g we read left-to-right), to stay consistant with its own format and what is common across other cloud services.

For our example circuit, IonQ’s raw results would return {'2': 0.5, '3': 0.5} with an exact simulator. - 2 is ‘10’ in binary, indicating \(q_0\) mesured in state \(|0\rangle\), and \(q_1\) in state \(|1\rangle\) - 3 is ‘11’ in binary, indicating \(q_0\) mesured in state \(|1\rangle\), and \(q_1\) in state \(|1\rangle\)

Tangelo returns {'01': 0.5, '11': 0.5}.

results = ionq_connection.job_results(job_id)
print(results)
Job info    ID:: dfb38687-8c70-4b40-833d-c6166a902efe    status :: completed 
{'01': 0.5, '11': 0.5}

Job cancel / delete

A wrapper called job_cancel provides a method to cancel before execution (if the job hasn’t run yet), or delete a job from the history. The cell below cancels / deletes the previously-submitted job: it therefore should not appear in the history anymore.

ionq_connection.job_cancel(job_id)
job_history = ionq_connection.job_get_history()
job_history
Job cancel  ID :: dfb38687-8c70-4b40-833d-c6166a902efe   status :: deleted 
id status target
0 b486e66a-2ae2-4275-8c9e-015a751dcbbe completed simulator
1 5dea41e0-ce02-4193-8e50-e48fc0cd0c22 canceled qpu.harmony
2 34cc2763-f617-427a-8b3a-621086946a3b completed qpu.harmony
3 777237b4-79e8-44d0-8ac6-762f24864e7a completed simulator
4 9da8401b-5334-4358-ab3f-ddcd3e2f1ff4 completed simulator
5 9dd67d5f-aa29-46fb-a707-1858d022aacc completed simulator
6 45e33c7e-33bd-43d6-aef3-87b78e7bdd77 completed simulator
7 be130388-8daf-49dd-8e17-5b4c5274d69f completed simulator
8 2c3526c0-44d4-4573-9bc1-c39a5c0135ae completed simulator
9 373f3175-e7be-48cd-bf47-ac5e331a0eb0 completed simulator
10 b567e71c-fa20-4fb9-9ce9-62521de7a1ec completed simulator
11 b1743e6d-359c-4175-bf56-99b8662fdcaf completed simulator
12 f9865e17-25d8-4d84-b8f8-dc31e07a8fae completed simulator
13 e54a5d12-65e4-4fef-8774-68584ccad94c completed simulator
14 be0c8bc7-5539-4e4f-b12c-5370a525076d completed simulator

4. Azure Quantum

A work in progress ! Though this package does not currently provide a way to directly submit jobs through Microsoft’s Azure Quantum cloud services, the tangelo.linq module can parse an abstract circuit and generate Q# code that can be written to file, or other of the formats supported by Azure Quantum (cirq, qiskit, IonQ…). Check out their documentation for the most reliable information.

Some users have successfully submitted experiments to Azure Quantum by simply translating their Tangelo circuit into the desired format and adding measurement gates at the end of their circuit.

Our generated Q# code is compatible with both the local QDK simulator (good for testing before submitting to an actual QPU) or by Azure Quantum. Submission through Azure Quantum will require the user to have an account on Azure, install the local CLI and Python dependencies. For an example of how one can use this package to first generate circuits, and then submit jobs through Azure quantum, please look into the example/qsharp folder of this package.

In the future, we intend to provide a connection object for Azure Quantum as well. Let us know if you would like us to prioritize this feature, or even help us implement it faster by contributing.