End-to-End Examples#

This chapter demonstrates using the RESTful Solver Application Programming Interface (SAPI) directly—without Ocean software providing the client—with two simple examples: submitting a problem to a Quantum Processing Unit (QPU) and to a quantum-classical hybrid solver

Attention

The examples in this guide are pedagogical: errors are not handled, for example. For a production-code implementation, see Ocean software’s cloud client package.

Import Python Packages Used in Examples#

The examples in this chapter use Python with the following packages:

>>> import base64
>>> import hashlib
>>> import json
>>> import requests
>>> import struct
>>> from urllib.parse import urlencode

Set Up the Session#

Ocean software documentation’s Configuring Access section explains how you can find your base URL and API token. Start by setting up the base URL and authentication token used for all SAPI requests.

  • SAPI endpoints are specific to a region. Examples in this document use the https://na-west-1.cloud.dwavesys.com/sapi/v2 base, for North America.

  • All requests to SAPI require users to authenticate using an API token. An API token is sent to SAPI in the form of HTTP header X-Auth-Token.

>>> SAPI_HOME = "https://na-west-1.cloud.dwavesys.com/sapi/v2"
... # Replace with your API token
>>> SAPI_TOKEN = "ABC-1234567...345678"     
...
>>> session = requests.Session()
>>> session.headers = {'X-Auth-Token': SAPI_TOKEN, 'Content-type': 'application/json'}

Retrieve Solvers Available for the Token#

Send a GET request method to the /solvers/remote resource. The optional filter parameter, used to reduce the quantity of retrieved information, can be omitted.

>>> filter = urlencode({"filter": "none,+id,+status,+avg_load,+properties.num_qubits,+properties.category"})
...
>>> r1 = session.get(f"{SAPI_HOME}/solvers/remote/?{filter}")
>>> print(r1.status_code)
200

The response contains all the solvers available to the API token. The code below lists the solver names, statuses, and current usage loads.

>>> r1 = r1.json()
>>> for i in range(len(r1)):
...    print(f"{r1[i]['id']} \n\tStatus: {r1[i]['status']}    Load: {r1[i]['avg_load']}")  
DW_2000Q_6
        Status: ONLINE    Load: 0.0
DW_2000Q_VFYC_6
        Status: ONLINE    Load: 0.0
hybrid_binary_quadratic_model_version2p
        Status: ONLINE    Load: 0.0
hybrid_discrete_quadratic_model_version1p
        Status: ONLINE    Load: 0.0
Advantage_system4.1
        Status: ONLINE    Load: 0.13
hybrid_constrained_quadratic_model_version1p
        Status: ONLINE    Load: 0.0
Advantage_system6.1
        Status: ONLINE    Load: 0.01
Advantage2_prototype1.1
        Status: ONLINE    Load: 0.0

Submit a Problem to a QPU Sampler#

This example of submitting a problem in Ising format, \(\text{E}_{ising}(\vc s) = \sum_{i=1}^N h_i s_i + \sum_{i=1}^N \sum_{j=i+1}^N J_{i,j} s_i s_j\), to a quantum computer uses the following simple problem with a single quadratic interaction:

\[\text{E} = -0.5x + 0.5y -xy.\]

Here, the linear and quadratic coefficients are stored in a dict.

>>> biases = {'x': -0.5, 'y': 0.5, 'xy': -1}

Select a Solver#

You might decide, for example, that your application is best suited to use an Advantage quantum computer, and select one with the largest working graph. This example selects one of the Advantage quantum computers available at the time of execution for the user account running the example.

>>> advantage_systems =  {r1[i]['id']: r1[i]["properties"]["num_qubits"] for
...    i in range(len(r1)) if "Advantage_system" in r1[i]['id']}
>>> akeys = list(advantage_systems.keys())
>>> avals = list(advantage_systems.values())
>>> qpu_solver = akeys[avals.index(max(avals))]
>>> print(qpu_solver)   
Advantage_system4.1

Send a GET request method to the /solvers/remote/<solver_id> resource.[1]

>>> r2 = session.get(f"{SAPI_HOME}/solvers/remote/{qpu_solver}")
>>> r2 = r2.json()

Format Your Problem for the Selected Solver#

Typically you map (minor-embed) your variables to a QPU’s qubits using a heuristic tool such as minorminor. This example is simple enough to just select the first coupler and the two qubits it couples.

>>> qubits = r2['properties']['qubits']
>>> couplers = r2['properties']['couplers']
...
>>> xy = couplers[0]
>>> print(f"Variable x is embedded as qubit {xy[0]} and y as {xy[1]}.")    
Variable x is embedded as qubit 30 and y as 31.

Next, format the biases as shown in the Submit Problems section. For less simple examples you will likely need to develop encoding functions similar to those of Ocean software’s cloud client package.

>>> lin_vec = len(qubits)*[float('nan')]
>>> lin_vec[qubits.index(xy[0])] = biases['x']
>>> lin_vec[qubits.index(xy[1])] = biases['y']
>>> lin_vec_encode =  base64.b64encode(struct.pack('<' + ('d' * len(lin_vec)), *lin_vec))
>>> lin = lin_vec_encode.decode("utf-8")
...
>>> quad_vec = [biases['xy']]
>>> quad_vec_encode =  base64.b64encode(struct.pack('<' + ('d' * len(quad_vec)), *quad_vec))
>>> quad = quad_vec_encode.decode("utf-8")

Submit Your SAPI Request#

Send a POST request method to the /problems resource with your configured request body, including the problem and solver parameters described in the Solver Properties and Parameters Reference guide. This example sets the number of required reads (anneals) and a problem label.

You should get a valid problem identifier in the response.

>>> r3 = session.post(f"{SAPI_HOME}/problems",
...                   json=[{"solver": qpu_solver,
...                          "label": "QPU REST submission 1",
...                          "data": {"format": "qp", "lin": lin, "quad": quad},
...                          "type": "ising",
...                          "params": {"num_reads": 10}}])
>>> r3 = r3.json()
>>> print(f"ID of submission is {r3[0]['id']}.")     
ID of submission is 29776c79-8893-48e7-b35e-d3618553fcb4.

Retrieve Your Solutions#

Send a GET request method to the /problems/<problem_id>/answer resource.

>>> r4 = session.get(f"{SAPI_HOME}/problems/{r3[0]['id']}/answer")    
>>> r4 = r4.json()     

Decode the Response#

As shown in the Retrieve Problem Information section, some fields of the response are binary encoded. Here too, less simple examples will likely require decoding functions similar to those of Ocean software’s cloud client package.

>>> qpu_access_time = r4['answer']['timing']['qpu_access_time']
...
>>> energies = base64.b64decode(r4['answer']['energies'])
>>> energies_decode = struct.unpack('<' + ('d' * (len(energies) // 8)), energies)
...
>>> print(f"Found lowest energy {min(energies_decode)} in {qpu_access_time} microseconds.")
Found lowest energy -1.0 in 15831.76 microseconds.

Submit a Problem to a Quantum-Classical Hybrid Solver#

This example submits a binary quadratic model (BQM) problem, in Ising format, \(\text{E}_{ising}(\vc s) = \sum_{i=1}^N h_i s_i + \sum_{i=1}^N \sum_{j=i+1}^N J_{i,j} s_i s_j\), to a quantum-classical hybrid BQM solver in the Leap service. The following simple problem with a single quadratic interaction is used:

\[\text{E} = -xy.\]

Upload Problem to the Leap Service#

Here, Ocean software is used to serialize the problem to the format described in the dimod to_file() method.

>>> import dimod
>>> bqm = dimod.BinaryQuadraticModel({}, {'xy': -1}, 'BINARY')
>>> bqm_ser = bqm.to_file().read()
Example serialized file

The serialized file may look like this:

b'DIMODBQM\x02\x00\xb2\x00\x00\x00{"dtype": "float64", "itype": "int32",
"ntype": "int32", "shape": [2, 1], "type": "BinaryQuadraticModel", "variables": true,
"vartype": "BINARY"}\n                                \x00\x00\x00\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xbf
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xbfVARS8\x00\x00
\x00["x", "y"]

The following code combines the POST request of Initiate Upload of Problem Data, the PUT request of Upload Problem Data, and the POST request of Submit a Checksum for a Problem Upload to execute a multi-part upload of the problem data. Calculations of the checksums for the part(s) and whole are shown there.

>>> size = len(bqm_ser)
>>> r5 = session.post(f"{SAPI_HOME}/bqm/multipart",
...     json={"size": size})
...
>>> problem_data_id = r5.json()["id"]
...
>>> hash_md5 = hashlib.md5()
>>> hash_md5.update(bqm_ser)
>>> part_hash = base64.b64encode(hash_md5.digest()).decode('utf-8')
...
>>> r6 = session.put(f"{SAPI_HOME}/bqm/multipart/{problem_data_id}/part/1",
...     headers={"Content-type": "application/octet-stream",
...              "Content-MD5": part_hash},
...     data=bqm_ser)

Select a Solver#

This example selects the first Binary Quadratic Model (BQM) hybrid solver available at the time of execution for the user account running the example. It uses the response r1 from the Retrieve Solvers Available for the Token section above.

>>> hybrid_bqm_solvers =  [r1[i]['id'] for i in range(len(r1)) if
...    r1[i]['properties']['category'] == "hybrid" and "binary" in r1[i]['id']]
>>> bqm_solver = hybrid_bqm_solvers[0]
>>> print(bqm_solver)                           
hybrid_binary_quadratic_model_version2p

Submit Your SAPI Request#

Format the request body as shown in the Submit Problems section. Also set the problem and solver parameters described in the Solver Properties and Parameters Reference guide. This example sets a value for the maximum solver runtime and a problem label.

Send a POST request method to the /problems resource. You should get a valid problem identifier in the response.

>>> r8 = session.post(f"{SAPI_HOME}/problems",
...                   json=[{"solver": bqm_solver,
...                          "label": "REST Submission to hybrid BQM solver 2",
...                          "data": {"format": "ref", "data": problem_data_id},
...                          "type": "bqm",
...                          "params": {"time_limit": 10}}])
>>> r8 = r8.json()
>>> problem_id = r8[0]['id']
>>> print(f"Problem identifier is {problem_id}.")   
Problem identifier is 8460ca28-ff9a-46be-bc7b-adac13e8348d.

Retrieve Your Solutions#

Send a GET request method to the /problems/<problem_id>/answer resource. Once the problem execution is finished, your solution and additional information is retrieved. Decode the answer as shown in the Retrieve a Problem section.

>>> r9 = session.get(f"{SAPI_HOME}/problems/{problem_id}")
>>> r9 = r9.json()
>>> if r9['status'] == 'COMPLETED':       
...   print(f"Run time was {r9['answer']['data']['info']['run_time']} microseconds.")
... else:
...   print("Not completed")
Run time was 9987459 microseconds.