|
@@ -27,17 +27,17 @@ class GraphState(object): |
|
|
|
|
|
|
|
|
def add_node(self, node, **kwargs): |
|
|
def add_node(self, node, **kwargs): |
|
|
""" Add a node. |
|
|
""" Add a node. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param node: The name of the node, e.g. ``9``, ``start`` |
|
|
:param node: The name of the node, e.g. ``9``, ``start`` |
|
|
:type node: Any hashable type |
|
|
:type node: Any hashable type |
|
|
:param kwargs: Any extra node attributes |
|
|
:param kwargs: Any extra node attributes |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Example of using node attributes :: |
|
|
Example of using node attributes :: |
|
|
|
|
|
|
|
|
>>> g.add_node(0, label="fred", position=(1,2,3)) |
|
|
>>> g.add_node(0, label="fred", position=(1,2,3)) |
|
|
>>> g.node[0]["label"] |
|
|
>>> g.node[0]["label"] |
|
|
fred |
|
|
fred |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
""" |
|
|
""" |
|
|
assert not node in self.node, "Node {} already exists".format(v) |
|
|
assert not node in self.node, "Node {} already exists".format(v) |
|
|
self.adj[node] = {} |
|
|
self.adj[node] = {} |
|
@@ -55,7 +55,7 @@ class GraphState(object): |
|
|
:param circuit: An iterable containing tuples of the form ``(node, operation)``. 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``. |
|
|
:param circuit: An iterable containing tuples of the form ``(node, operation)``. 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):: |
|
|
Example (makes a Bell pair):: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>>> g.act_circuit([(0, "hadamard"), (1, "hadamard"), ((0, 1), "cz")]) |
|
|
>>> g.act_circuit([(0, "hadamard"), (1, "hadamard"), ((0, 1), "cz")]) |
|
|
|
|
|
|
|
|
""" |
|
|
""" |
|
@@ -93,17 +93,22 @@ class GraphState(object): |
|
|
for n in v) |
|
|
for n in v) |
|
|
return tuple(edges) |
|
|
return tuple(edges) |
|
|
|
|
|
|
|
|
def remove_vop(self, a, avoid): |
|
|
|
|
|
""" Reduces VOP[a] to the identity """ |
|
|
|
|
|
others = set(self.adj[a]) - {avoid} |
|
|
|
|
|
|
|
|
def remove_vop(self, node, avoid): |
|
|
|
|
|
""" Attempts to remove the vertex operator on a particular qubit. |
|
|
|
|
|
|
|
|
|
|
|
:param node: The node whose vertex operator should be reduced to the identity. |
|
|
|
|
|
:param avoid: We will try to leave this node alone during the process (if possible). |
|
|
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
|
others = set(self.adj[node]) - {avoid} |
|
|
if self.deterministic: |
|
|
if self.deterministic: |
|
|
swap_qubit = min(others) if others else avoid |
|
|
swap_qubit = min(others) if others else avoid |
|
|
else: |
|
|
else: |
|
|
swap_qubit = others.pop() if others else avoid |
|
|
swap_qubit = others.pop() if others else avoid |
|
|
|
|
|
|
|
|
for v in reversed(clifford.decompositions[self.node[a]["vop"]]): |
|
|
|
|
|
|
|
|
for v in reversed(clifford.decompositions[self.node[node]["vop"]]): |
|
|
if v == "x": |
|
|
if v == "x": |
|
|
self.local_complementation(a, "U ->") |
|
|
|
|
|
|
|
|
self.local_complementation(node, "U ->") |
|
|
else: |
|
|
else: |
|
|
self.local_complementation(swap_qubit, "V ->") |
|
|
self.local_complementation(swap_qubit, "V ->") |
|
|
|
|
|
|
|
@@ -122,7 +127,7 @@ class GraphState(object): |
|
|
""" Act a local rotation on a qubit |
|
|
""" Act a local rotation on a qubit |
|
|
|
|
|
|
|
|
:param node: The index of the node to act on |
|
|
:param node: The index of the node to act on |
|
|
:param operation: The Clifford-group operation to perform. |
|
|
|
|
|
|
|
|
:param operation: The Clifford-group operation to perform. You can use any of the names in the :ref:`Clifford group alias table <clifford>`. |
|
|
""" |
|
|
""" |
|
|
rotation = clifford.by_name[str(operation)] |
|
|
rotation = clifford.by_name[str(operation)] |
|
|
self.node[node]["vop"] = clifford.times_table[ |
|
|
self.node[node]["vop"] = clifford.times_table[ |
|
@@ -165,15 +170,17 @@ class GraphState(object): |
|
|
if new_edge != edge: |
|
|
if new_edge != edge: |
|
|
self._toggle_edge(a, b) |
|
|
self._toggle_edge(a, b) |
|
|
|
|
|
|
|
|
def measure(self, node, basis, force=None): |
|
|
|
|
|
""" Measure in an arbitrary basis |
|
|
|
|
|
|
|
|
def measure(self, node, basis, force=None, detail=False): |
|
|
|
|
|
""" Measure in an arbitrary basis |
|
|
|
|
|
|
|
|
:param node: The name of the qubit to measure. |
|
|
:param node: The name of the qubit to measure. |
|
|
:param basis: The basis in which to measure. |
|
|
:param basis: The basis in which to measure. |
|
|
:type basis: :math:`\in` ``{"px", "py", "pz"}`` |
|
|
:type basis: :math:`\in` ``{"px", "py", "pz"}`` |
|
|
:param force: Measurements in quantum mechanics are probabilistic. If you want to force a particular outcome, use the ``force``. |
|
|
:param force: Measurements in quantum mechanics are probabilistic. If you want to force a particular outcome, use the ``force``. |
|
|
:type force: boolean |
|
|
:type force: boolean |
|
|
|
|
|
|
|
|
|
|
|
:param detail: Provide detailed information |
|
|
|
|
|
:type detail: boolean |
|
|
|
|
|
|
|
|
""" |
|
|
""" |
|
|
basis = clifford.by_name[basis] |
|
|
basis = clifford.by_name[basis] |
|
|
ha = clifford.conjugation_table[self.node[node]["vop"]] |
|
|
ha = clifford.conjugation_table[self.node[node]["vop"]] |
|
@@ -186,11 +193,11 @@ class GraphState(object): |
|
|
result = not result |
|
|
result = not result |
|
|
|
|
|
|
|
|
if basis == clifford.by_name["px"]: |
|
|
if basis == clifford.by_name["px"]: |
|
|
result = self._measure_x(node, result) |
|
|
|
|
|
|
|
|
result, determinate = self._measure_graph_x(node, result) |
|
|
elif basis == clifford.by_name["py"]: |
|
|
elif basis == clifford.by_name["py"]: |
|
|
result = self._measure_y(node, result) |
|
|
|
|
|
|
|
|
result, determinate = self._measure_graph_y(node, result) |
|
|
elif basis == clifford.by_name["pz"]: |
|
|
elif basis == clifford.by_name["pz"]: |
|
|
result = self._measure_z(node, result) |
|
|
|
|
|
|
|
|
result, determinate = self._measure_graph_z(node, result) |
|
|
else: |
|
|
else: |
|
|
raise ValueError("You can only measure in {X,Y,Z}") |
|
|
raise ValueError("You can only measure in {X,Y,Z}") |
|
|
|
|
|
|
|
@@ -198,7 +205,51 @@ class GraphState(object): |
|
|
if phase == -1: |
|
|
if phase == -1: |
|
|
result = not result |
|
|
result = not result |
|
|
|
|
|
|
|
|
return result |
|
|
|
|
|
|
|
|
if detail: |
|
|
|
|
|
return {"result": int(result), |
|
|
|
|
|
"determinate": (determinate or force!=None), |
|
|
|
|
|
"conjugated_basis": basis, |
|
|
|
|
|
"phase": phase, |
|
|
|
|
|
"node": node, |
|
|
|
|
|
"force": force} |
|
|
|
|
|
else: |
|
|
|
|
|
return int(result) |
|
|
|
|
|
|
|
|
|
|
|
def measure_x(self, node, force=None, detail=False): |
|
|
|
|
|
""" Measure in the X basis |
|
|
|
|
|
|
|
|
|
|
|
:param node: The name of the qubit to measure. |
|
|
|
|
|
:param force: Measurements in quantum mechanics are probabilistic. If you want to force a particular outcome, use the ``force``. |
|
|
|
|
|
:type force: boolean |
|
|
|
|
|
:param detail: Provide detailed information |
|
|
|
|
|
:type detail: boolean |
|
|
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
|
return self.measure(node, "px", force, detail) |
|
|
|
|
|
|
|
|
|
|
|
def measure_y(self, node, force=None, detail=False): |
|
|
|
|
|
""" Measure in the Y basis |
|
|
|
|
|
|
|
|
|
|
|
:param node: The name of the qubit to measure. |
|
|
|
|
|
:param force: Measurements in quantum mechanics are probabilistic. If you want to force a particular outcome, use the ``force``. |
|
|
|
|
|
:type force: boolean |
|
|
|
|
|
:param detail: Provide detailed information |
|
|
|
|
|
:type detail: boolean |
|
|
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
|
return self.measure(node, "py", force, detail) |
|
|
|
|
|
|
|
|
|
|
|
def measure_z(self, node, force=None, detail=False): |
|
|
|
|
|
""" Measure in the Z basis |
|
|
|
|
|
|
|
|
|
|
|
:param node: The name of the qubit to measure. |
|
|
|
|
|
:param force: Measurements in quantum mechanics are probabilistic. If you want to force a particular outcome, use the ``force``. |
|
|
|
|
|
:type force: boolean |
|
|
|
|
|
:param detail: Provide detailed information |
|
|
|
|
|
:type detail: boolean |
|
|
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
|
return self.measure(node, "pz", force, detail) |
|
|
|
|
|
|
|
|
def _toggle_edges(self, a, b): |
|
|
def _toggle_edges(self, a, b): |
|
|
""" Toggle edges between vertex sets a and b """ |
|
|
""" Toggle edges between vertex sets a and b """ |
|
@@ -211,10 +262,10 @@ class GraphState(object): |
|
|
done.add((j, i)) |
|
|
done.add((j, i)) |
|
|
self._toggle_edge(i, j) |
|
|
self._toggle_edge(i, j) |
|
|
|
|
|
|
|
|
def _measure_x(self, node, result): |
|
|
|
|
|
""" Measure the graph in the X-basis """ |
|
|
|
|
|
|
|
|
def _measure_graph_x(self, node, result): |
|
|
|
|
|
""" Measure the bare graph in the X-basis """ |
|
|
if len(self.adj[node]) == 0: |
|
|
if len(self.adj[node]) == 0: |
|
|
return 0 |
|
|
|
|
|
|
|
|
return 0, True |
|
|
|
|
|
|
|
|
# Pick a vertex |
|
|
# Pick a vertex |
|
|
if self.deterministic: |
|
|
if self.deterministic: |
|
@@ -247,10 +298,10 @@ class GraphState(object): |
|
|
for n in a - {friend}: |
|
|
for n in a - {friend}: |
|
|
self._toggle_edge(friend, n) |
|
|
self._toggle_edge(friend, n) |
|
|
|
|
|
|
|
|
return result |
|
|
|
|
|
|
|
|
return result, False |
|
|
|
|
|
|
|
|
def _measure_y(self, node, result): |
|
|
|
|
|
""" Measure the graph in the Y-basis """ |
|
|
|
|
|
|
|
|
def _measure_graph_y(self, node, result): |
|
|
|
|
|
""" Measure the bare graph in the Y-basis """ |
|
|
# Do some rotations |
|
|
# Do some rotations |
|
|
for neighbour in self.adj[node]: |
|
|
for neighbour in self.adj[node]: |
|
|
self._update_vop(neighbour, "sqz" if result else "msqz") |
|
|
self._update_vop(neighbour, "sqz" if result else "msqz") |
|
@@ -260,13 +311,12 @@ class GraphState(object): |
|
|
for i, j in it.combinations(vngbh, 2): |
|
|
for i, j in it.combinations(vngbh, 2): |
|
|
self._toggle_edge(i, j) |
|
|
self._toggle_edge(i, j) |
|
|
|
|
|
|
|
|
|
|
|
# TODO: naming: # lcoS.herm_adjoint() if result else lcoS |
|
|
self._update_vop(node, 5 if result else 6) |
|
|
self._update_vop(node, 5 if result else 6) |
|
|
# TODO: naming: # lcoS.herm_adjoint() if result else |
|
|
|
|
|
# lcoS |
|
|
|
|
|
return result |
|
|
|
|
|
|
|
|
return result, False |
|
|
|
|
|
|
|
|
def _measure_z(self, node, result): |
|
|
|
|
|
""" Measure the graph in the Z-basis """ |
|
|
|
|
|
|
|
|
def _measure_graph_z(self, node, result): |
|
|
|
|
|
""" Measure the bare graph in the Z-basis """ |
|
|
# Disconnect |
|
|
# Disconnect |
|
|
for neighbour in tuple(self.adj[node]): |
|
|
for neighbour in tuple(self.adj[node]): |
|
|
self._del_edge(node, neighbour) |
|
|
self._del_edge(node, neighbour) |
|
@@ -280,7 +330,7 @@ class GraphState(object): |
|
|
else: |
|
|
else: |
|
|
self._update_vop(node, "hadamard") |
|
|
self._update_vop(node, "hadamard") |
|
|
|
|
|
|
|
|
return result |
|
|
|
|
|
|
|
|
return result, False |
|
|
|
|
|
|
|
|
def order(self): |
|
|
def order(self): |
|
|
""" Get the number of qubits """ |
|
|
""" Get the number of qubits """ |
|
@@ -302,11 +352,11 @@ class GraphState(object): |
|
|
|
|
|
|
|
|
def to_json(self, stringify=False): |
|
|
def to_json(self, stringify=False): |
|
|
""" Convert the graph to JSON-like form. |
|
|
""" Convert the graph to JSON-like form. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param stringify: JSON keys must be strings, But sometimes it is useful to have a JSON-like object whose keys are tuples. |
|
|
:param 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 do disk, do something like this:: |
|
|
If you want to dump a graph do disk, do something like this:: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>>> import json |
|
|
>>> import json |
|
|
>>> with open("graph.json") as f: |
|
|
>>> with open("graph.json") as f: |
|
|
json.dump(graph.to_json(True), f) |
|
|
json.dump(graph.to_json(True), f) |
|
@@ -326,15 +376,15 @@ class GraphState(object): |
|
|
# TODO |
|
|
# TODO |
|
|
|
|
|
|
|
|
def to_state_vector(self): |
|
|
def to_state_vector(self): |
|
|
""" Get the full state vector corresponding to this stabilizer state. Useful for debugging, interface with other simulators. |
|
|
|
|
|
|
|
|
""" 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! |
|
|
This method becomes very slow for more than about ten qubits! |
|
|
|
|
|
|
|
|
The output state is represented as a ``abp.qi.CircuitModel``:: |
|
|
The output state is represented as a ``abp.qi.CircuitModel``:: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>>> print g.to_state_vector() |
|
|
>>> print g.to_state_vector() |
|
|
|00000>: 0.18+0.00j |
|
|
|00000>: 0.18+0.00j |
|
|
|00001>: 0.18+0.00j ... |
|
|
|00001>: 0.18+0.00j ... |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.. todo:: |
|
|
.. todo:: |
|
|
Doesn't work with non-``int`` node labels |
|
|
Doesn't work with non-``int`` node labels |
|
|
|
|
|
|
|
@@ -370,6 +420,14 @@ class GraphState(object): |
|
|
""" Check equality between GraphStates """ |
|
|
""" Check equality between GraphStates """ |
|
|
return self.adj == other.adj and self.node == other.node |
|
|
return self.adj == other.adj and self.node == other.node |
|
|
|
|
|
|
|
|
|
|
|
def copy(self): |
|
|
|
|
|
""" Make a copy of this graphstate """ |
|
|
|
|
|
g = GraphState() |
|
|
|
|
|
g.node = self.node.copy() |
|
|
|
|
|
g.adj = self.adj.copy() |
|
|
|
|
|
g.deterministic = self.deterministic |
|
|
|
|
|
return g |
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
if __name__ == '__main__': |
|
|
g = GraphState() |
|
|
g = GraphState() |
|
|
g.add_nodes(range(10)) |
|
|
g.add_nodes(range(10)) |
|
|