Making a custom device model

In the quickstart section, we have discussed the default devices for both analog and digital quantum computing. In this document, we will show how to define a device from scratch.

Nevertheless, we strongly recommend to contact ParityQC first, so that we can provide a customized device model for your hardware that takes maximum advantage of the hardware-specific features of the ParityOS compiler.

Device models

Custom device models can be defined by creating a new device model class, derived from the DeviceModelBase class:

class MyDeviceModel(DeviceModelBase):
    """
    Describes my splendid quantum computing device.
    """
The relevant attributes of such a class are:
qubit_connections

A list of connections of qubits that are implemented as direct interactions on the device. All qubits should be labeled with two-dimensional coordinates, as in Qubit((i, j)).

device_type

The type of this device, can either be 'cnot' or 'plaquette'.

preset

Determines the parameters that are passed on to the compiler in order to optimize the code for the device. Standard values are 'analog_default' and 'digital_default'. Customer-specific presets can be provided upon request.

Analog device model

In this example we will define a custom device which has 5 qubits in the following layout:

(0, 1) -- (1, 1) -- (2, 1)
  |          |     /
  |          |   /
(0, 0) -- (1, 0)

Where on the square we can have a 4-body constraint as well as all 4 possible 3-body constraints. On the triangle on the left, only a 3-body constraint is possible. All qubits have the ability to get a local field in the Z direction.

We start by labeling all the qubit sites. We just have to assign a unique label to each qubit. This can be a string, an integer or a coordinate as in the above example.:

qubit_sites = Qubit((0, 0)), Qubit((1, 0)), Qubit((0, 1)), Qubit((1, 1)), Qubit((2, 1))

Then for the qubit_connections we have to specify that each qubit can get a local field, which is a single-qubit collection. For example, a local field on qubit Qubit((0, 0)) would become {Qubit((0, 0))}. In the above example, the local fields are:

local_fields = [{qubit} for qubit in qubit_sites]

The next step is to define all possible constraints that are possible on the device, where each constraint is a collection of 3 or 4 qubits. The order of the qubits in the constraint, as well as the order of the constraints does not matter:

constraint_connections = [
    {Qubit((0, 0)), Qubit((0, 1)), Qubit((1, 0)), Qubit((1, 1))},
    {Qubit((0, 0)), Qubit((0, 1)), Qubit((1, 0))},
    {Qubit((0, 0)), Qubit((0, 1)), Qubit((1, 1))},
    {Qubit((0, 0)), Qubit((1, 0)), Qubit((1, 1))},
    {Qubit((0, 1)), Qubit((1, 0)), Qubit((1, 1))},
    {Qubit((1, 0)), Qubit((1, 1)), Qubit((2, 1))},
]

Where the first element is the 4-body constraint, after which the 4 possible 3-body constraints on the left plaquette are listed. Finally, there is the 3-body constraint on the right plaquette.

Now we have all the information we need to define the device model, where it is important that we set device_type = 'plaquette'. We can also specify a preset for the compiler, e.g. preset = 'default_analog':

from parityos.device_model import DeviceModelBase

class MyDeviceModel(DeviceModelBase):
    qubit_connections = local_fields + constraint_connections
    device_type = 'plaquette'
    preset = 'analog_default'

So to define the custom device, you can run the following code:

from parityos import Qubit
from parityos.device_model import DeviceModelBase

qubit_sites = Qubit((0, 0)), Qubit((1, 0)), Qubit((0, 1)), Qubit((1, 1)), Qubit((2, 1))
local_fields = [{qubit} for qubit in qubit_sites]

constraint_connections = [
    {Qubit((0, 0)), Qubit((0, 1)), Qubit((1, 0)), Qubit((1, 1))},
    {Qubit((0, 0)), Qubit((0, 1)), Qubit((1, 0))},
    {Qubit((0, 0)), Qubit((0, 1)), Qubit((1, 1))},
    {Qubit((0, 0)), Qubit((1, 0)), Qubit((1, 1))},
    {Qubit((0, 1)), Qubit((1, 0)), Qubit((1, 1))},
    {Qubit((1, 0)), Qubit((1, 1)), Qubit((2, 1))},
]

class MyDeviceModel(DeviceModelBase):
    qubit_connections = local_fields + constraint_connections
    device_type = 'plaquette'
    preset = 'analog_default'

my_device_model = MyDeviceModel()

Note

If you want to try out this custom device, use the standard submission (see Submitting a Job to the Compiler) to compile the following optimization problem:

optimization_problem = ProblemRepresentation(
    interactions=[{Qubit(0), Qubit(1)},
                  {Qubit(1), Qubit(2)},
                  {Qubit(1), Qubit(3)},
                  {Qubit(0), Qubit(3)},
                  {Qubit(2), Qubit(3)}],
    coefficients=[1, 0.5, -0.7, 0.8, -0.3],
)

where you set the device_model to the one we defined here!

Digital device model

In this example we will define a custom device which has 5 qubits in the following layout:

(0, 1) -- (1, 1) -- (2, 1)
  |          |
  |          |
(0, 0) -- (1, 0)

On all qubits that are connected with a dotted line, it is possible to do a CNOT gate. All qubits have the ability to get a local field in the Z direction.

We start by defining all the qubit sites (the order does not matter):

qubit_sites = Qubit((0, 0)), Qubit((1, 0)), Qubit((0, 1)), Qubit((1, 1)), Qubit((2, 1))

Then for the qubit_connections we have to specify that each qubit can get a local field, which is a single-qubit collection. For example, a local field on qubit Qubit((0, 0)) would be a collection with a single qubit, {Qubit((0, 0))}. The local fields are:

local_fields = [{qubit} for qubit in qubit_sites]

The next step is to define all possible CNOT connection that are possible on the device. The order of the elements in the connections does not matter, it is assumed that a CNOT can be performed in both directions. This results in the following lists of connections, where each connection is of length 2:

constraint_connections = [
    {Qubit((0, 0)), Qubit((0, 1))},
    {Qubit((0, 1)), Qubit((1, 1))},
    {Qubit((0, 0)), Qubit((1, 0))},
    {Qubit((1, 0)), Qubit((1, 1))},
    {Qubit((1, 1)), Qubit((2, 1))},
]

Now we have all the information we need to define the DeviceModel, where it is important that we set device_type = 'cnot'. In order to use the standard compiler for digital devices, we add the attribute preset = 'default_digital':

from parityos.device_model import DeviceModelBase

class MyDigitalDeviceModel(DeviceModelBase):
    qubit_connections = local_fields + constraint_connections
    device_type = 'cnot'
    preset = 'digital_default'

So to define the custom device, you can run the following code:

from parityos import Qubit
from parityos.device_model import DeviceModelBase

qubit_sites = Qubit((0, 0)), Qubit((1, 0)), Qubit((0, 1)), Qubit((1, 1)), Qubit((2, 1))
local_fields = [{qubit} for qubit in qubit_sites]
constraint_connections = [
    {Qubit((0, 0)), Qubit((0, 1))},
    {Qubit((0, 1)), Qubit((1, 1))},
    {Qubit((0, 0)), Qubit((1, 0))},
    {Qubit((1, 0)), Qubit((1, 1))},
    {Qubit((1, 1)), Qubit((2, 1))},
]

class MyDigitalDeviceModel(DeviceModelBase):
    qubit_connections = local_fields + constraint_connections
    device_type = 'cnot'
    preset = 'digital_default'

my_digital_device_model = MyDigitalDeviceModel()

Note

If you want to try out this custom device, use the standard submission (see Submitting a Job to the Compiler) to compile the following optimization problem:

optimization_problem = ProblemRepresentation(
    interactions=[{Qubit(0), Qubit(1)},
                  {Qubit(1), Qubit(2)},
                  {Qubit(1), Qubit(3)},
                  {Qubit(0), Qubit(3)},
                  {Qubit(2), Qubit(3)}],
    coefficients=[1, 0.5, -0.7, 0.8, -0.3],
)

where you set the device_model to the my_digital_device_model defined above.