ProjectQ 101 - Part 1

Published by projectq (01/21/2021)

Starting with ProjectQ

This series of posts will focus on introducing ProjectQ and its syntax and highlights some of its particularities and unique features compared to other quantum computing frameworks such as Qiskit, Cirq or Q# for example.

ProjectQ in a few words

ProjectQ is a full-stack quantum computing framework, mostly written in Python, that aims to provide an easy to use and easily extensible syntax for programming quantum computers. Like most of its competitors, it is hardware agnostic and provides all the basic functionalities required to build, simulate or execute quantum programs on your laptop, on HPC clusters or even on some actual hardware. It even provides some advanced functionalities rarely found in other similar frameworks such as the compute/uncompute programming pattern, but more on those operations in a future post.
At the time of this writing, ProjectQ is naturally available on StrangeWorks, but is also available on Google Colaboratory for those of you that like to use online Jupyter notebooks. It is also compatible with the following online platforms:

Your first quantum program using ProjectQ

Let's immediately start with your first ProjectQ quantum program. For now we will keep it as simple as possible to introduce the ProjectQ syntax as well as the basic concepts behind ProjectQ.
The code below is essentially a random bit generator. In it, we allocate a single qubit and then put it in a superposition state using a Hadamard gate. After that, we measure it and lastly access the result of the measurement experiment, which in our case will be either '0' or '1' with 50% probability for each case.
from projectq import MainEngine
from projectq.ops import H, Measure

eng = MainEngine()

qubit = eng.allocate_qubit()

H | qubit
Measure | qubit


print(f"Measured: {int(qubit)}")
Let's now go through the code above line by line and explain things as we go along.

Import statements

The first few lines are reserved for some Python import statements:
from projectq import MainEngine
from projectq.ops import H, Measure
There are a few submodules under the main projectq module. At the time of this writing, these are:
  • backends
  • cengines
  • libs
  • meta
  • ops
  • setups
  • types
The ones that are highlighted in bold face are those you will be dealing with most of the time. The other two are for some more advanced features that you might want to look at once you get more familiar with ProjectQ.
The meta and setups sub-modules will be covered in more details in future post of this series.
​Quick note: the MainEngine class is actually located in the projectq.cengines sub-module. It is imported into the main ProjectQ module for convenience.

The MainEngine

Each quantum computing software framework needs a point of entry so that the user can communicate with the compiler so that it can build and then simulate or run some quantum program.
eng = MainEngine()
In the case of ProjectQ, this role is performed by the MainEngine. Without going into too many details, the main engine is basically responsible for creating (or allocating) new qubits. It is also the one responsible for orchestrating all the operations that will be performed on the quantum program upon request (e.g. optimising a quantum circuit by cancelling gates where possible or by decomposing some quantum gate into some other quantum gates). Once again, we will probably cover all the functions of the MainEngine in a future post; for now it is sufficient to realise that the MainEngine in a ProjectQ program is your main point of entry with the compiler backend and mainly serves to allocate new qubits as well as call the flush() operations which will cover in a section later in this post.

Allocating qubits

Before you can do anything in a quantum program, you need to ask the compiler to give you access to some qubits. In the case of a ProjectQ this can be done using the MainEngine:
qubit = eng.allocate_qubit()

As shown in the line above, creating (or allocating) a new qubit with ProjectQ is as simple as calling the corresponding method on the MainEngine instance.

It is also possible to request more than one qubit at a time by calling the following function:

qureg = eng.allocate_qureg(10)

In the example above, we have allocate a register with 10 qubits.

Quantum operations

Now that we have allocated some qubits, it is time to perform some operations with them.
H | qubit
Measure | qubit

The syntax ProjectQ uses to describe the application of some quantum gate to some qubits is closely related to the bra-ket notation used in Physics. In this notation, a quantum state is commonly represented as ψ| \psi \rangle, while some mathematical or physical operator applied to that qubit is usually represented by ​AψA |\psi\rangle​.

ProjectQ tries to mimic this syntax as closely as possible but must ultimately do away with the \rangle​ part of the notation as the code would not be valid Python code otherwise.

So in order to apply any operation on some qubits, you simply need to write the operator you are interested in followed by a pipe symbol '|' then followed by the qubits this operator applies to. For now we have only covered the syntax for single qubit gates. We will cover the case of multiple qubit gates in the next post of this series.

All the commonly used gates can be found inside the projectq.ops sub-module. Here are a few that are commonly used:

  • Hadamard gate: H
  • Pauli-X gate: X
  • Pauli-Y gate: Y
  • Pauli-Z gate: Z
  • Rotation along the x-axis: Rx
  • Rotation along the y-axis: Ry
  • Rotation along the z-axis: Rz
  • Measurement: Measure

If the gate requires one or more parameters, you can specify them by passing those to the gate when applying it to some qubits:

Rx(1.0) | qubit

Flushing the circuit

For reasons that will become clearer in the future, it is important to call the flush() method of the MainEngine every time you want to make sure that the compiler has processed all of the operations until a certain point.

This is particularly important when you intend to access the results of some measurements as you need to make sure that all the operations have been correctly processed by the compiler before you can access the result of your experiment.

Accessing the results of measurements

The way to access measurement results using ProjectQ is extremely simple.
print(f"Measured: {int(qubit)}")

Simply cast the qubit object to either a boolean using the bool or to an integer using the int Python builtin functions.


If you made it so far, we have now introduced the basic functionalities of ProjectQ using the example of a simple random bit generator. You should now be capable of writing your own simple ProjectQ programs. However, we still have not covered multiple qubit gates or even how to performed controlled gate operations. Stay tuned for a future post.