Running QiskitPattern using decorators (Experimental)

In this tutorial we will describe alternative way (interface) of running your patterns.

This new interface provides a way to define pattern as python function and run it in a single file, using @distribute_qiskit_function decorator. All you need to do is annotate function with @distribute_qiskit_function decorator and call it. As a result of the call of the function you will get Job handle to check it’s progress like we did in previous tutorials.

Limitations of this interface:

  • Functions decorated with distribute_qiskit_function, can only accept named arguments for now. E.g do not use my_pattern(argument1), instead specify name of the argument my_pattern(argument1=argument1)

  • Function return will run qiskit_serverless.save_result function under the hood, which means return values must be json serializable values in form of dictionary (with values as all Python native types, like strings, lists, dicts, numpy arrays, QuantumCircuit, Operator, etc.)

  • When using local folder/modules user must specify working_dir as ./ (current folder), which will be archiving and sending content of entire folder for remote execution. Make sure that folder does not have large files.

⚠ This interface is experimental, therefore it is subjected to breaking changes.

⚠ This provider is set up with default credentials to a test cluster intended to run on your machine. For information on setting up infrastructure on your local machine, check out the guide on local infrastructure setup.

[1]:
import os
from qiskit_serverless import ServerlessClient

provider = ServerlessClient(
    token=os.environ.get("GATEWAY_TOKEN", "awesome_token"),
    host=os.environ.get("GATEWAY_HOST", "http://localhost:8000"),
)
provider
[1]:
<ServerlessProvider: gateway-provider>

Hello, Qiskit!

Let’s create simpliest pattern by writing a funtion hello_qiskit and annotating it with @distribute_qiskit_function decorator. The distribute_qiskit_function decorator accepts a BaseProvider instance for the provider argument. Other arguments are dependencies to specify extra packages to install during execution and working_dir to specify working directory that will be shiped for remote execution if needed.

[2]:
from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler as Sampler

from qiskit_serverless import distribute_qiskit_function, distribute_task, get


@distribute_qiskit_function(provider)
def hello_qiskit():
    circuit = QuantumCircuit(2)
    circuit.h(0)
    circuit.cx(0, 1)
    circuit.measure_all()
    circuit.draw()

    sampler = Sampler()
    quasi_dists = sampler.run([(circuit)]).result()[0].data.meas.get_counts()

    return quasi_dists


job = hello_qiskit()
job
[2]:
<Job | 3793be13-e434-4136-acc1-037c36805320>
[3]:
job.result()
[3]:
[{'0': 0.4999999999999999, '3': 0.4999999999999999}]

QiskitPattern with distributed tasks

As in previous examples you can define distributed tasks and call them within a pattern.

[4]:
from qiskit_serverless import get_arguments, save_result, distribute_task, get
from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler as Sampler
from qiskit.circuit.random import random_circuit


@distribute_task(target={"cpu": 2})
def distributed_sample(circuit: QuantumCircuit):
    """Distributed task that returns quasi distribution for given circuit."""
    return Sampler().run([(circuit)]).result()[0].data.meas.get_counts()


@distribute_qiskit_function(provider)
def pattern_with_distributed_tasks(circuits):
    sample_task_references = [distributed_sample(circuit) for circuit in circuits]
    results = get(sample_task_references)
    print(results)


circuits = []
for _ in range(3):
    circuit = random_circuit(2, 2)
    circuit.measure_all()
    circuits.append(circuit)

job = pattern_with_distributed_tasks(circuits=circuits)
job
[4]:
<Job | 611d4070-6845-45d3-9e29-9a0ec4e12a49>
[5]:
job.result()
print(job.logs())
OpenBLAS WARNING - could not determine the L2 cache size on this system, assuming 256k
OpenBLAS WARNING - could not determine the L2 cache size on this system, assuming 256k
2023-11-29 07:44:50,932 INFO worker.py:1354 -- Using address 172.18.0.2:6379 set in the environment variable RAY_ADDRESS
2023-11-29 07:44:50,933 INFO worker.py:1489 -- Connecting to existing Ray cluster at address: 172.18.0.2:6379...
2023-11-29 07:44:50,951 INFO worker.py:1664 -- Connected to Ray cluster. View the dashboard at 172.18.0.2:8265 
(pid=1191) OpenBLAS WARNING - could not determine the L2 cache size on this system, assuming 256k
[[{0: 1.0}], [{0: 0.5, 1: 0.5}], [{2: 1.0}]]
(pid=1193) OpenBLAS WARNING - could not determine the L2 cache size on this system, assuming 256k [repeated 5x across cluster] (Ray deduplicates logs by default. Set RAY_DEDUP_LOGS=0 to disable log deduplication, or see https://docs.ray.io/en/master/ray-observability/ray-logging.html#log-deduplication for more options.)

QiskitPattern with local modules/folders

Situation with local folders is a little bit trickier. In order to make local imports work in remote execution of a pattern we need to specify working_dir argument for distribute_qiskit_function decorator. It will tell qiskit serverless to ship all content of current folder to remote cluster, which will make local folders discoverable by Python interpreter during remote execution.

In this example we will use local folder source_files with circuit_utils.py file, which has implementation of create_hello_world_circuit function.

[6]:
import os
from qiskit_serverless import distribute_qiskit_function, distribute_task, get, save_result
from qiskit.primitives import StatevectorSampler as Sampler

from source_files.circuit_utils import create_hello_world_circuit


@distribute_qiskit_function(provider, working_dir="./")
def my_function_with_modules():
    quasi_dists = Sampler().run([(create_hello_world_circuit())]).result()[0].data.meas.get_counts()
    return {"quasi_dists": quasi_dists}


job = my_function_with_modules()
job
[6]:
<Job | 01bbe84c-cd15-49e8-aca0-126c5abc13ec>
[7]:
job.result()
[7]:
{'quasi_dists': [{'0': 0.4999999999999999, '3': 0.4999999999999999}]}