# abp¶

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\}$$.

## Installing¶

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  ## Quickstart¶ 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 --------- X X X  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] 3  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  ## Visualization¶ 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 Parameters: 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$$. act_circuit(circuit) 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 Parameters: a – The first qubit b – The second qubit act_czs(*pairs) Shorthand to act many CZs act_hadamard(qubit) Shorthand for self.act_local_rotation(qubit, "hadamard") act_local_rotation(node, operation) Act a local rotation on a qubit Parameters: 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. Parameters: 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["label"] fred  copy() Make a copy of this graphstate del_qubit(node) Remove a qubit. TODO: this is a hack right now. edgelist() Describe a graph as an edgelist # TODO: inefficient from_json(data) 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 local_complementation(v) As defined in LISTING 1 of Anders & Briegel measure(node, basis, force=None, detail=False, friend=None) Measure in an arbitrary basis Parameters: 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 Parameters: 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 order() Get the number of qubits pull(url=None) Loads the state from the server push() Shares the state on the server and displays browser remove_vop(node, avoid) Attempts to remove the vertex operator on a particular qubit. Parameters: 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). to_json(stringify=False) 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)  to_stabilizer() 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  to_state_vector() 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: abp.clifford.use_old_cz() Use the CZ lookup table from A&B’s code, rather than our own. Useful for testing. ## 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.

## Reference¶

More detailed docs are available here: