Quickstart

Let’s walk through the steps needed in order to compile an optimization problem in the Parity Architecture via ParityOS. We will first need to initialize a parityos.CompilerClient object. Then, in order to make a submission to the ParityQC API, we will need an optimization problem to compile and a target device on which we want to compile the problem. Finally, we will show how to access the results.

Note

For an easy to copy-paste version of the code snippets discussed just below, see the Examples at the end of this section.

Initializing the Client

After you have successfully installed ParityOS, it is possible to submit problems for compilation. For this you need a username and password, which we will provide.

There are three ways you can provide your username and password to the Client.

  1. Pass the username directly to the constructor of CompilerClient:

    compiler_client = CompilerClient(username)
    

    For security reasons, avoid storing username and password in plain text in your codebase.

  2. Set the variables PARITYOS_USER and PARITYOS_PASS in your environment; the CompilerClient will use them as username and password. Therefore, you can initialize it as follows:

    compiler_client = CompilerClient()
    
  3. If the credentials cannot be found by the CompilerClient with the methods above, it will prompt you to enter the credentials manually.

Defining an optimization problem

There are multiple ways to define an optimization problem to submit; for example, the ProblemRepresentation class in ParityOS has a class method that allows you to initialize it from a networkx.Graph.

Here we will instead use default initialization, which is uses the ProblemRepresentation class. The ProblemRepresentation describes the interactions in the problem and allows you to add optional multiplicative constraints. The ProblemRepresentation can be made in the following way:

problem_representation = ProblemRepresentation(
    interactions=[{Qubit(0), Qubit(1)},
                  {Qubit(1), Qubit(2)},
                  {Qubit(2), Qubit(0)}],
    coefficients=[1, 0.5, -0.7],
)

where each term is a collection of qubits and a strength.

Note

This problem representation is equivalent to \(H = s_0 s_1 + 0.5 s_1 s_2 - 0.7 s_0 s_2\).

Note

The Hamiltonian may also include single-qubit terms. For example, for \(H = s_0 + 0.5 s_0 s_1\)

problem_representation = ProblemRepresentation(
    interactions=[{Qubit(0)}, {Qubit(0), Qubit(1)}],
    coefficients=[1, 0.5],
)

Thus, we can define an optimization problem using the ProblemRepresentation class:

optimization_problem = ProblemRepresentation(
    interactions=[{Qubit(0)}, {Qubit(0), Qubit(1)}],
    coefficients=[1, 0.5],
)

Note

It is possible to include multiplicative constraints in the optimization problem using the constraints field, which is empty by default. It can be initialized using an iterable of ParityConstraint objects. A ParityConstraint can be made using a collection of qubits and a parity. For example, the constraint \(s_1 \cdot s_2 \cdot s_4 = 1\), can be included as:

optimization_problem = ProblemRepresentation(
    interactions=[{Qubit(0)}, {Qubit(0), Qubit(1)}],
    coefficients=[1, 0.5],
    constraints = [
        ParityConstraint({Qubit(1), Qubit(2), Qubit(4)}, parity=1),
    ],
)

Defining a Target Device

A DeviceModel object represents the target device of the compilation process; that is, the compiler will try to map a given optimization problem, with possibly non-quadratic interactions and constraints, to a quantum computer with a specific topology of interactions that most probably does not coincide with the interaction graph of the optimization problem.

The target device is defined by the qubits and connections which are available on it. There are a few standard device models available for use. For analog quantum computing, there is an easy way to initialize a rectangular device which has, aside from local fields, a three- or four-body coupler for every plaquette of nearest-neighbor qubits:

x, y = 2, 2   # the dimensions of the device
device_model = RectangularAnalogDevice(x, y)

Similarly, for digital (gate-based) quantum computing, you can easily create a rectangular device that has, aside from single-body gates, CNOT connections between all vertical and horizontal nearest neighbor qubits:

x, y = 2, 2   # the dimensions of the device
device_model = RectangularDigitalDevice(x, y)

Note

It is also possible to define a custom device with specific qubit sites and qubit connections (see Making a custom device model). In that case, it is important to define all connections on the device, including the local fields.

Submitting a Job to the Compiler

Now we have all the information we need to try to compile the optimization problem. We can submit the optimization problem to the compiler as follows:

parityos_output = compiler_client.compile(optimization_problem, device_model)

This command will connect to the ParityOS server over the internet in order to compile the optimization problem to the a Parity Architecture for the device model. If the compilation was successful, the CompilerClient.compile method will return a ParityOSOutput object that contains all the information about the compiled problem.

Note

The default compiler presets defined in the RectangularAnalogDevice or RectangularDigitalDevice do not always result in optimal results. It is possible to receive from ParityQC a personalized preset, which is optimized for the problems and devices you are interested in. Contact us to know more.

Note

The CompilerClient.compile method is a synchronous call, which blocks the execution of code until the compilation process is finished. There is an asynchronous method for submitting problems and retrieving the output of the compiler; see Asynchronous submissions.

Note

In case of a connection time-out or server-side problem, a ParityOSException will be raised.

The ParityOS output

If the compilation was successful, the returned ParityOSOutput object will contain all the information about the compiled problem as attributes:

  • compiled_problem: contains the interactions and constraints in the Parity Architecture layout, in the form of a ProblemRepresentation object just like the original optimization problem.

  • mappings: provides the mapping between the optimization problem (logical qubits) and the compiled problem (physical qubits):

    • mappings.logical_degeneracies: lists how logical degeneracies are fixed;

    • mappings.encoding_map: details how physical qubits map onto the logical qubits;

    • mappings.decoding_map: maps each logical qubit to the set of physical qubits whose product of spin components results in the spin of the logical qubit.

For digital devices, the ParityOS output object also contains the details of optimized gate sequences:

  • constraint_circuit: provides an optimized circuit that decomposes the propagators that implement the parity constraints (of the form \(e^{-i \frac{\theta}{2} Z_1 Z_2 Z_3}\) or \(e^{-i \frac{\theta}{2} Z_1 Z_2 Z_3 Z_4}\)) into native gate sequences for the quantum device;

  • driver_circuit: provides an optimized circuit to implement driver terms for the QAOA algorithm on the quantum device.

  • initial_state_preparation_circuit: provides a circuit that initializes the quantum state in a suitable starting state for the QAOA algorithm, starting from a state where all qubits are in the 0 state.

Examples

Note

When running the following code, make sure that the PARITYOS_USER and PARITYOS_PASS variables are loaded into the environment.

This is the full code needed to run the discussed analog example:

from parityos import CompilerClient, ProblemRepresentation, Qubit, RectangularAnalogDevice


compiler_client = CompilerClient()
optimization_problem = ProblemRepresentation(
    interactions=[{Qubit(0), Qubit(1)}, {Qubit(1), Qubit(2)}, {Qubit(2), Qubit(0)}],
    coefficients=[1, 0.5, -0.7],
)
x, y = 2, 2  # the dimensions of the device
device_model = RectangularAnalogDevice(x, y)
parityos_output = compiler_client.compile(optimization_problem, device_model)
print(parityos_output.compiled_problem)

And for the digital example:

from parityos import CompilerClient, ProblemRepresentation, Qubit, RectangularDigitalDevice


compiler_client = CompilerClient()
optimization_problem = ProblemRepresentation(
    interactions=[{Qubit(0), Qubit(1)}, {Qubit(1), Qubit(2)}, {Qubit(2), Qubit(0)}],
    coefficients=[1, 0.5, -0.7],
)
x, y = 2, 2  # the dimensions of the device
device_model = RectangularDigitalDevice(x, y)
parityos_output = compiler_client.compile(optimization_problem, device_model)
print(parityos_output.compiled_problem)