This is the documentation for abp. It’s a work in progress.

abp is a Python port of Anders and Briegel’ s method for fast simulation of Clifford circuits. That means that you can make quantum states of thousands of qubits, perform any sequence of Clifford operations, and measure in any of \(\{\sigma_x, \sigma_y, \sigma_z\}\).


You can install from pip:

$ pip install --user abp==0.6.3

Alternatively, clone from the github repo and run setup.py:

$ git clone https://github.com/peteshadbolt/abp
$ cd abp
$ python setup.py install --user

If you want to modify and test abp without having to re-install, switch into develop mode:

$ python setup.py develop --user


Let’s make a new GraphState object with a register of three qubits:

>>> from abp import GraphState
>>> g = GraphState(3)

All the qubits are initialized by default in the \(|+\rangle\) state:

>>> print g.to_state_vector()
|000❭:       √1/8   + i √0
|100❭:       √1/8   + i √0
|010❭:       √1/8   + i √0
|110❭:       √1/8   + i √0
|001❭:       √1/8   + i √0
|101❭:       √1/8   + i √0
|011❭:       √1/8   + i √0
|111❭:       √1/8   + i √0

We can also check the stabilizer tableau:

>>> print g.to_stabilizer()
 0  1  2

Or look directly at the vertex operators and neighbour lists:

>>> print g
0:  IA      -
1:  IA      -
2:  IA      -

This representation might be unfamiliar. Each row shows the index of the qubit, then the vertex operator, then a list of neighbouring qubits. To understand vertex operators, read the original paper by Anders and Briegel.

Let’s act a Hadamard gate on the zeroth qubit – this will evolve qubit 0 to the \(H|+\rangle = |1\rangle\) state:

>>> g.act_hadamard(0)
>>> print g.to_state_vector()
|000❭:       √1/4   + i √0
|010❭:       √1/4   + i √0
|001❭:       √1/4   + i √0
|011❭:       √1/4   + i √0
>>> print g
0:  YC      -
1:  IA      -
2:  IA      -

And now run some CZ gates:

>>> g.act_cz(0,1)
>>> g.act_cz(1,2)
>>> print g
0:  YC      -
1:  IA      (2,)
2:  IA      (1,)
>>> print g.to_state_vector()
|000❭:       √1/4   + i √0
|010❭:       √1/4   + i √0
|001❭:       √1/4   + i √0
|011❭:      -√1/4   + i √0

Tidy up a bit:

>>> g.del_node(0)
>>> g.act_hadamard(0)
>>> print g.to_state_vector()
|00❭:        √1/2   + i √0
|11❭:        √1/2   + i √0

Cool, we made a Bell state. Incidentally, those those state vectors and stabilizers are genuine Python objects, not just stringy representations of the state:

>>> g = abp.GraphState(2)
>>> g.act_cz(0, 1)
>>> g.act_hadamard(0)
>>> psi = g.to_state_vector()
>>> print psi
|00❭:        √1/2   + i √0
|11❭:        √1/2   + i √0

psi is a state vector – i.e. it is an exponentially large vector of complex numbers. We can still run gates on it:

>>> psi.act_cnot(0, 1)
>>> psi.act_hadamard(0)
>>> print psi
|00❭:        √1     + i √0

But these operations will be very slow. Let’s have a look at the stabilizer tableau:

>>> tab = g.to_stabilizer()
>>> print tab
 0  1
 Z  Z
 X  X
>>> print tab.tableau
{0: {0: 3, 1: 3}, 1: {0: 1, 1: 1}}
>>> print tab[0, 0]

Quantum mechanics is nondeterminstic. However, it’s often useful to get determinstic behaviour for testing purposes. You can force abp to behave determinstically by setting:

>>> abp.DETERMINSTIC = True


You can visualize states in 3D using the tool at https://abv.peteshadbolt.co.uk/. At some point I will merge the code for that server into this repo.

In order to visualize states you must give each node a position attribute:

>>> g.add_qubit(0, position={"x": 0, "y":0, "z":0}, vop="identity")
>>> g.add_qubit(0, position={"x": 1, "y":0, "z":0}, vop="identity")

There’s a utility function in abp.util to construct those dictionaries:

>>> from abp.util import xyz
>>> g.add_qubit(0, position=xyz(0, 0, 0), vop="identity")
>>> g.add_qubit(1, position=xyz(1, 0, 0), vop="identity")

Then it’s really easy to get a 3D picture of the state:

>>> g.push()
Shared state to https://abv.peteshadbolt.co.uk/lamp-moon-india-leopard

That’s a secret URL that you can use to collaboratively edit and view graph states in the browser. There are only a few billion such URLs so it should not be considered extremely secure. If you want, you can also load an existing state:

>>> g = GraphState()
>>> g.pull("https://abv.peteshadbolt.co.uk/lamp-moon-india-leopard")
>>> g.show()

GraphState API

The abp.GraphState class is the main interface to abp.

class abp.GraphState(data=(), vop='identity')

This is the main class used to model stabilizer states. Internally it uses the same dictionary-of-dictionaries data structure as networkx.

__init__(data=(), vop='identity')

Construct a GraphState

  • data – An iterable of nodes used to construct the graph, or an integer – the number of nodes, or a nx.Graph.
  • vop – The default VOP for new qubits. Setting vop="identity" initializes qubits in \(|+\rangle\). Setting vop="hadamard" initializes qubits in \(|0\rangle\).

Run many gates in one call.

Parameters:circuit – An iterable containing tuples of the form (operation, node). If operation is a name for a local operation (e.g. 6, hadamard) then that operation is performed on node. If operation is cz then a CZ is performed on the two nodes in node.

Example (makes a Bell pair):

>>> g.act_circuit([("hadamard", 0), ("hadamard", 1), ("cz", (0, 1))])
act_cz(a, b)

Act a controlled-phase gate on two qubits

  • a – The first qubit
  • b – The second qubit

Shorthand to act many CZs


Shorthand for self.act_local_rotation(qubit, "hadamard")

act_local_rotation(node, operation)

Act a local rotation on a qubit

  • node – The index of the node to act on
  • operation – The Clifford-group operation to perform. You can use any of the names in the Clifford group alias table.
add_node(*args, **kwargs)

Add a node

add_qubit(name, **kwargs)

Add a qubit to the state.

  • name (Any hashable type) – The name of the node, e.g. 9, start.
  • kwargs – Any extra node attributes

By default, qubits are initialized in the \(|0\rangle\) state. Provide the optional vop argument to set the initial state.

Example of using node attributes

>>> g._add_node(0, label="fred", position=(1,2,3))
>>> g.node[0]["label"]

Make a copy of this graphstate


Remove a qubit. TODO: this is a hack right now.


Describe a graph as an edgelist # TODO: inefficient


Construct the graph from JSON data :param data: JSON data to be read.

has_edge(v1, v2)

Test existence of an edge between two vertices


As defined in LISTING 1 of Anders & Briegel

measure(node, basis, force=None, detail=False, friend=None)

Measure in an arbitrary basis

  • node – The name of the qubit to measure.
  • basis (\(\in\) {"px", "py", "pz"}) – The basis in which to measure.
  • friend (Any neighbour of node.) – Specify a node to toggle about when performing an \(X\) measurement.
  • force (boolean) – Forces the measurement outcome.
  • detail (boolean) – Get detailed information.

Measurements in quantum mechanics are probabilistic. If you want to force a particular outcome \(\in\{0, 1\}\), use force.

You can get more information by setting detail=True, in which case measure() returns a dictionary with the following keys:

  • outcome: the measurement outcome.
  • determinate: indicates whether the outcome was determinate or random. For example, measuring \(|0\rangle\) in \(\sigma_x\) always gives a deterministic outcome. determinate is overridden by force – forced outcomes are always determinate.
  • conjugated_basis: The index of the measurement operator, rotated by the vertex operator of the measured node, i.e. \(U_\text{vop} \sigma_m U_\text{vop}^\dagger\).
  • phase: The phase of the cojugated basis, \(\pm 1\).
  • node: The name of the measured node.
  • force: The value of force.
measure_sequence(measurements, forces=None, detail=False)

Measures a sequence of Paulis

  • measurements – The sequence of measurements to be made, in the form [(node, basis), …]
  • force (list) – Measurements in quantum mechanics are probabilistic. If you want to force a particular outcome, use the force. List outcome force values in same order as measurements
  • detail (boolean) – Provide detailed information
measure_x(node, force=None, detail=False, friend=None)

Measure in the X basis

measure_y(node, force=None, detail=False)

Measure in the Y basis

measure_z(node, force=None, detail=False)

Measure in the Z basis


Get the number of qubits


Loads the state from the server


Shares the state on the server and displays browser

remove_vop(node, avoid)

Attempts to remove the vertex operator on a particular qubit.

  • node – The node whose vertex operator should be reduced to the identity.
  • avoid – We will try to leave this node alone during the process (if possible).

Convert the graph to JSON-like form.

Parameters:stringify – JSON keys must be strings, But sometimes it is useful to have a JSON-like object whose keys are tuples.

If you want to dump a graph to disk, do something like this:

>>> import json
>>> with open("graph.json") as f:
        json.dump(graph.to_json(True), f)

Get the stabilizer representation of the state:

>>> print g.to_stabilizer()
   0    1    2    3    100  200
   X    Z    Z    X
   Z    X    Z
   Z    Z    X
 - Z              Z
                       X    Z
                       Z    X
Get the full state vector corresponding to this stabilizer state. Useful for debugging, interface with other simulators.
This method becomes very slow for more than about ten qubits!

The output state is represented as a abp.qi.CircuitModel:

>>> print g.to_state_vector()
|00000❭: 0.18+0.00j
|00001❭: 0.18+0.00j ...

The Clifford group

This module handles operations on the Clifford group. It makes extensive use of the lookup tables in abp.tables. The code to generate those tables is included in this distribution as abp/build_tables.py This package emumerates and labels the single-qubit Clifford group, and provides methods for matrix multiplication and conjugation. It also includes the look-up table for the CZ gate.

There are 24 members of the single-qubit Clifford group. You can refer to some of them by multiple names. The complete set of aliases for single-qubit Cliffords is as follows:

Index Aliases
0 IA, identity, identity_h
1 XA, px, px_h
2 YA, py, py_h
3 ZA, pz, pz_h
4 IB
5 XB, sqz, msqz_h, phase_h
6 YB, msqz, sqz_h, phase
7 ZB
8 IC
9 XC, msqy, sqy_h
10 YC, hadamard, hadamard_h
11 ZC, sqy, msqy_h
12 ID
13 XD
14 YD, sqx, msqx_h
15 ZD, msqx, sqx_h
16 IE
17 XE
18 YE
19 ZE
20 IF
21 XF
22 YF
23 ZF

The clifford module provides a few useful functions:


Use the CZ lookup table from A&B’s code, rather than our own. Useful for testing.


abp has a bunch of tests. It tests against all sorts of things, including the circuit model, Anders & Briegels’ original code, Scott Aaronson’s chp, and common sense. You can run all the tests using pytest:

$ pytest
53 tests run in 39.5 seconds (53 tests passed)

Currently I use some reference implementations of chp and graphsim which you won’t have installed, so some tests will be skipped. That’s expected.


More detailed docs are available here: