Callisto tket framework¶
The tket framework is a quantum software framework primarily used for the development and execution of quantum algorithms (quantum circuits). It is designed to be platform-agnostic software, and there are a lot of extensions available which allow tket to interface with backends from a range of providers ( see tket extension ).
Due to its popularity and the broad amount of available backends using this framework, we wrote an extension to tket that enables a user to communicate with the C12 emulator Callisto . The backend name is CallistoBackend
.
We need to create a circuit using the Circuit
class to run a quantum algorithm.
[1]:
from pytket import Circuit
circuit = Circuit(2)
circuit.X(0)
circuit.CX(0, 1)
print(circuit.get_commands())
[X q[0];, CX q[0], q[1];]
Internally all circuits in the pytket framework are represented as directed acyclic graphs (DAG). Using ptyket util package it is possible to see the corresponding DAG of the chosen circuit.
A classical model for quantum computing is a model where the main program is run on the host computer, which occasionally sends off jobs to the quantum computer and retrieves the results. A backend represents a connection to some instance, either quantum hardware or an emulator. It presents a uniform interface for submitting circuits to be processed and retrieving the results. The main goal of this approach is to promote the development of platform-independent software, helping the code that the developers write to be more future-proof.
Using tket’s abstract class, Backend,
it is possible to set different characteristics of a device that it should represent. The main properties that a backend need to implement are:
The restrictions that are specific to specific quantum devices. Different constraints are encoded using the predicates, which are essentially a boolean property of a circuit that must return True to run the circuit on the target machine.
Sending a circuit to the target device and examining the results. This process can be accomplished by calling
Backend.process_circuit()
orBackend.process_circuits()
methods. These methods will send a circuit for execution and return an instance ofResultHandle
as a result of their execution. TheResultHandle
is a unique identifier that can be used to retrieve the actual results once the job has finished.Retrieval of the results. Obtaining the results of a successfully run job is done using the
Backend.get_result()
method, which returns an instance ofBackendResult
class. The classBackendResult
has methods that can be useful for obtaining different pieces of information about the job that has been run. These methods are:get_state()
- get the state vectorget_shots()
- get the shots. The shots are returned as a 2D array of the result for each shot.get_counts()
- get the counts (it is obtained from the shots directly)get_density_matrix()
- get the density matrix of the job.
To implement the C12 emulator Callisto, we have developed CallistoBackend
class with all the above options incorporated.
[2]:
import os
os.environ["C12_TOKEN"] = "08071c09-4131-4369-ae5d-76be96a8cd86"
os.environ["C12_HOST_URL"]= "api.dev-simulator.c12qe.net"
[3]:
import os
from c12_callisto_clients.pytket.extensions.callisto import CallistoBackend
access_token = os.getenv("C12_TOKEN") # Token that is obtained for allowing the access to the system
backend_name = "c12sim-iswap"
# create callisto backend instance
callisto = CallistoBackend(backend_name, token=access_token, verbose=False)
# Get the ResultHandle instance
handle = callisto.process_circuit(circuit, n_shots=1024)
print(handle)
('0b022428-9ae3-4190-9129-3f4743905208',)
[4]:
job_uuid = handle[0]
# Get BackendResult instance
job_result = callisto.get_result(handle)
print(callisto.circuit_status(handle)) # Get the status of the circuit
print(job_result.get_shots())
print(job_result.get_counts())
CircuitStatus(status=<StatusEnum.COMPLETED: 'Circuit has completed. Results are ready.'>, message='', error_detail=None, completed_time=None, queued_time=None, submitted_time=None, running_time=None, cancelled_time=None, error_time=None, queue_position=None)
[[1 1]
[1 1]
[1 1]
...
[1 1]
[1 1]
[1 1]]
Counter({(np.uint8(1), np.uint8(1)): np.int64(1024)})
[5]:
circuit2 = Circuit(20)
for n in range(1, 20):
circuit2.X(n)
circuit2.CX(0, n)
handle2 = callisto.process_circuit(circuit2, n_shots=1024)
# This should fail due to MaxNQubitsPredicate which controls the number of qubits that can be run on the emulator
---------------------------------------------------------------------------
CircuitNotValidError Traceback (most recent call last)
Cell In[5], line 6
3 circuit2.X(n)
4 circuit2.CX(0, n)
----> 6 handle2 = callisto.process_circuit(circuit2, n_shots=1024)
8 # This should fail due to MaxNQubitsPredicate which controls the number of qubits that can be run on the emulator
File ~/Desktop/Projects/simulator/C12QEsimulator/src/c12_callisto_clients/pytket/extensions/callisto/backends/callisto.py:321, in CallistoBackend.process_circuit(self, circuit, n_shots, valid_check, **kwargs)
317 """
318 Run a single circuit.
319 """
320 if valid_check:
--> 321 self._check_all_circuits([circuit])
323 n_shots = 1024 if n_shots is None else n_shots
324 result_type = "counts,statevector,density_matrix"
File /opt/anaconda3/envs/c12_callisto_clients/lib/python3.10/site-packages/pytket/backends/backend.py:126, in Backend._check_all_circuits(self, circuits, nomeasure_warn)
120 errors = (
121 CircuitNotValidError(i, repr(pred))
122 for pred in self.required_predicates
123 if not pred.verify(circ)
124 )
125 for error in errors:
--> 126 raise error
127 if nomeasure_warn:
128 if circ.n_gates_of_type(OpType.Measure) < 1:
CircuitNotValidError: Circuit with index 0 in submitted does not satisfy MaxNQubitsPredicate(13) (try compiling with backend.get_compiled_circuits first).
[ ]: