Configuring Storage and Executors, Submitting to Quantinuum, Using Predefined Workers¶
In this example we’re covering a finer grained control over the workflow run.
These are the internals that are set inside the run_workflow function.
For this we’re going to construct a graph thad runs a pytket circuit on a simulator using the quantinuum_worker
The worker can be installed using pip install tkr-quantinuum-worker
Opaque Types¶
Tierkreis can use any type that is serializable as in and outputs.
To use such types for type hinting you can use the OpaqueType.
%pip install tierkreis pytket qnexus
from tierkreis.controller.data.models import OpaqueType
Circuit = OpaqueType["pytket._tket.circuit.Circuit"]
BackendResult = OpaqueType["pytket.backends.backendresult.BackendResult"]
Now we can construct a graph using the quantinuum_worker.
The api definitions for workers build by the tierkreis teams are already included in tierkreis.
Still it is necessary to install the worker manually.
We define a graph Circuit -> BackendResult using hardcoded information for which emulator backend to use.
from tierkreis.builder import GraphBuilder
from tierkreis.controller.data.models import TKR
from tierkreis.quantinuum_worker import (
compile_using_info,
get_backend_info,
run_circuit,
)
g = GraphBuilder(TKR[Circuit], TKR[BackendResult])
info = g.task(get_backend_info(device_name=g.const("H2-1")))
compiled_circuit = g.task(compile_using_info(g.inputs, info))
results = g.task(
run_circuit(
circuit=compiled_circuit,
n_shots=g.const(10),
device_name=g.const("H2-1SC"),
),
)
g.outputs(results)
Now we will define our own storage and executor. Storage is responsible for setting up the checkpointing; it stores the state of the computation. We have to provide a uuid, and optionally a name, as before.
from uuid import UUID
from tierkreis.storage import FileStorage
storage = FileStorage(UUID(int=209), do_cleanup=True, name="quantinuum_submission")
Since the quantinuum_worker is a python worker we will use uv to run it.
An executor lives in context, where it can access workers to run their main entrypoints.
We define this by providing a path to the directory our workers live in, in this case in the tierkreis_workers directory.
from tierkreis.consts import PACKAGE_PATH
from tierkreis.executor import UvExecutor
executor = UvExecutor(PACKAGE_PATH.parent / "tierkreis_workers", storage.logs_path)
Since this graph is using the qnexus api internally you also need to run the following once:
from qnexus.client.auth import login
login()
Once we provide the graph inputs we can now run it by providing a storage and an executor.
from pathlib import Path
from pytket.qasm.qasm import circuit_from_qasm
from tierkreis import run_graph
circuit = circuit_from_qasm(Path().parent / "data" / "ghz_state_n23.qasm")
run_graph(storage, executor, g, circuit)
And finally we can print the outputs
from tierkreis.storage import read_outputs
outputs = read_outputs(g, storage)