This merge updates abpserver to support a full set of operations including creation / deletion, gates, local complementation, and measurements through the web interface. So now you can click on qubits and measure them in X or whatever. I've tested it but I bet there are bugs -- please raise an issue if you find one. Conflicts: examples/visualization/grid_2d.pymaster
| @@ -43,7 +43,7 @@ class GraphState(graphstate.GraphState, nx.Graph): | |||
| time.sleep(delay) | |||
| except websocket._exceptions.WebSocketTimeoutException: | |||
| print "Timed out ... you might be pushing a bit hard" | |||
| sys.exit(0) | |||
| time.sleep(delay) | |||
| #self.ws.close() | |||
| #self.connect_to_server() | |||
| @@ -42,6 +42,21 @@ class GraphState(object): | |||
| for n in range(data): | |||
| self._add_node(n, vop=vop) | |||
| def add_node(self, *args, **kwargs): | |||
| """ Add a node """ | |||
| self._add_node(self, *args, **kwargs) | |||
| def _del_node(self, node): | |||
| """ Remove a node. TODO: this is a hack right now! """ | |||
| if not node in self.node: | |||
| return | |||
| del self.node[node] | |||
| for k in self.adj[node]: | |||
| del self.adj[k][node] | |||
| del self.adj[node] | |||
| def _add_node(self, node, **kwargs): | |||
| """ Add a node. By default, nodes are initialized with ``vop=``:math:`I`, i.e. they are in the :math:`|+\\rangle` state. | |||
| @@ -56,7 +71,10 @@ class GraphState(object): | |||
| fred | |||
| """ | |||
| assert not node in self.node, "Node {} already exists".format(v) | |||
| if node in self.node: | |||
| print "Warning: node {} already exists".format(node) | |||
| return | |||
| default = kwargs.get("default", "identity") | |||
| self.adj[node] = {} | |||
| self.node[node] = {} | |||
| @@ -405,9 +423,6 @@ class GraphState(object): | |||
| >>> with open("graph.json") as f: | |||
| json.dump(graph.to_json(True), f) | |||
| .. todo:: | |||
| Implement ``from_json()``! | |||
| """ | |||
| if stringify: | |||
| node = {str(key): value for key, value in self.node.items()} | |||
| @@ -417,6 +432,13 @@ class GraphState(object): | |||
| else: | |||
| return {"node": self.node, "adj": self.adj} | |||
| def from_json(self, data): | |||
| """ Construct the graph from JSON data | |||
| :param data: JSON data to be read. | |||
| """ | |||
| self.node = data["node"] | |||
| self.adj = data["adj"] | |||
| def to_state_vector(self): | |||
| """ 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! | |||
| @@ -29,16 +29,14 @@ | |||
| <div id=node_name></div> | |||
| <ul> | |||
| <li id=node_vop></li> | |||
| <!--<li><a href="#">Measure in X</a></li>--> | |||
| <!--<li><a href="#">Measure in Y</a></li>--> | |||
| <!--<li><a href="#">Measure in Z</a></li>--> | |||
| <!--<li><a href="#">Act Hadamard</a></li>--> | |||
| <!--<li><a href="#">Act Phase</a></li>--> | |||
| <!--<li><a href="#" onclick="editor.localComplementation()">Invert neighbourhood</a></li>--> | |||
| <!--<li>--> | |||
| <!--<a href="#">IA</a>--> | |||
| <!--<a href="#">IB</a>--> | |||
| <!--</li>--> | |||
| <li>Measure in | |||
| <a href="#" onclick="editor.measureX()">X</a> / | |||
| <a href="#" onclick="editor.measureY()">Y</a> / | |||
| <a href="#" onclick="editor.measureZ()">Z</a></li> | |||
| <li>Act | |||
| <a href="#" onclick="editor.hadamard()">Hadamard</a> / | |||
| <a href="#" onclick="editor.phase()">Phase</a></li> | |||
| <li><a href="#" onclick="editor.localComplementation()">Invert neighbourhood</a></li> | |||
| <li><a href="#" onclick="editor.deleteNode()">Delete</a></li> | |||
| </ul> | |||
| </div> | |||
| @@ -26,6 +26,9 @@ html, body { margin: 0; padding: 0; overflow: hidden; font-size: 10pt; font-fam | |||
| font-size: 9pt; | |||
| } | |||
| #node_name { | |||
| font-size: 12pt; | |||
| } | |||
| #node_data { | |||
| background-color: black; | |||
| @@ -41,80 +41,19 @@ abj.has_edge = function(a, b) { | |||
| return Object.prototype.hasOwnProperty.call(abj.adj[a], b); | |||
| }; | |||
| abj.toggle_edge = function(a, b) { | |||
| if (abj.has_edge(a, b)) { | |||
| abj.del_edge(a, b); | |||
| } else { | |||
| abj.add_edge(a, b); | |||
| } | |||
| }; | |||
| abj.get_swap = function(node, avoid) { | |||
| for (var i in abj.adj[node]) { | |||
| if (i != avoid) { | |||
| return i; | |||
| } | |||
| } | |||
| return avoid; | |||
| }; | |||
| abj.remove_vop = function(node, avoid) { | |||
| var swap_qubit = abj.get_swap(node, avoid); | |||
| var decomposition = tables.decompositions[abj.node[node].vop]; | |||
| for (var i = decomposition.length - 1; i >= 0; --i) { | |||
| var v = decomposition[i]; | |||
| abj.local_complementation(v == "x" ? node : swap_qubit); | |||
| } | |||
| }; | |||
| abj.local_complementation = function(node) { | |||
| // TODO: inefficient | |||
| var done = {}; | |||
| for (var i in abj.adj) { | |||
| for (var j in abj.adj[i]) { | |||
| var name = i>j ? [i,j] : [j,i]; | |||
| if (done[name]===false){ | |||
| abj.toggle_edge(i, j); | |||
| done[name] = true; | |||
| } | |||
| } | |||
| abj.node[i].vop = tables.times_table[abj.node[i].vop][6]; | |||
| } | |||
| abj.node[node].vop = tables.times_table[abj.node[node].vop][14]; | |||
| }; | |||
| abj.act_local_rotation = function(node, operation) { | |||
| var rotation = tables.clifford[operation]; | |||
| abj.node[node].vop = tables.times_table[rotation][abj.node[node].vop]; | |||
| }; | |||
| abj.act_hadamard = function(node) { | |||
| abj.act_local_rotation(node, 10); | |||
| }; | |||
| abj.is_sole_member = function(group, node) { | |||
| // TODO: this is slow as heck | |||
| var keys = Object.keys(group); | |||
| return keys.length == 1 && keys[0] == node; | |||
| }; | |||
| abj.act_cz = function(a, b) { | |||
| if (abj.is_sole_member(abj.adj[a], b)) { | |||
| abj.remove_vop(a, b); | |||
| } | |||
| if (abj.is_sole_member(abj.adj[b], a)) { | |||
| abj.remove_vop(b, a); | |||
| } | |||
| if (abj.is_sole_member(abj.adj[a], b)) { | |||
| abj.remove_vop(a, b); | |||
| } | |||
| var edge = abj.has_edge(a, b); | |||
| var new_state = tables.cz_table[edge ? 1 : 0][abj.node[a].vop][abj.node[b].vop]; | |||
| abj.node[a].vop = new_state[1]; | |||
| abj.node[b].vop = new_state[2]; | |||
| if (new_state[0] != edge) { | |||
| abj.toggle_edge(a, b); | |||
| } | |||
| abj.update = function(newState) { | |||
| abj.node = newState.node; | |||
| abj.adj = newState.adj; | |||
| }; | |||
| abj.order = function(){ | |||
| return Object.keys(abj.node).length; | |||
| }; | |||
| abj.edgelist = function() { | |||
| @@ -131,18 +70,4 @@ abj.edgelist = function() { | |||
| return output; | |||
| }; | |||
| abj.log_graph_state = function() { | |||
| console.log(JSON.stringify(abj.node)); | |||
| console.log(JSON.stringify(abj.adj)); | |||
| }; | |||
| abj.update = function(newState) { | |||
| abj.node = newState.node; | |||
| abj.adj = newState.adj; | |||
| }; | |||
| abj.order = function(){ | |||
| return Object.keys(abj.node).length; | |||
| }; | |||
| @@ -32,14 +32,10 @@ editor.onFreeMove = function() { | |||
| }; | |||
| editor.focus = function(node) { | |||
| editor.grid.position.copy(abj.node[node].position); | |||
| gui.controls.target.copy(abj.node[node].position); | |||
| gui.hideNodeMessage(); | |||
| editor.selection = node; | |||
| gui.serverMessage("Selected node " + node + "."); | |||
| node_name.innerHTML = "Node " + node; | |||
| node_data.className = "visible"; | |||
| node_vop.innerHTML = "VOP: " + abj.node[node].vop; | |||
| //gui.serverMessage("Selected node " + node + "."); | |||
| }; | |||
| editor.addQubitAtPoint = function(point) { | |||
| @@ -47,19 +43,13 @@ editor.addQubitAtPoint = function(point) { | |||
| return; | |||
| } | |||
| point.round(); | |||
| // Check for clashes | |||
| for (var node in abj.node) { | |||
| var delta = new THREE.Vector3(); | |||
| delta.subVectors(abj.node[node].position, point); | |||
| if (delta.length()<0.1){ return; } | |||
| var new_node = Math.floor(point.x) + "," + Math.floor(point.y) + "," + Math.floor(point.z); | |||
| if (Object.prototype.hasOwnProperty.call(abj.node, new_node)) { | |||
| gui.serverMessage("Node " + new_node +" already exists."); | |||
| return; | |||
| } | |||
| // TODO: This SUCKS | |||
| var new_node = point.x + "." + point.y + "." + point.z; | |||
| abj.add_node(new_node, { position: point, vop:0 }); | |||
| websocket.edit({action:"create", name:new_node, position: point}); | |||
| editor.focus(new_node); | |||
| graph.update(); | |||
| gui.serverMessage("Created node " + new_node +"."); | |||
| }; | |||
| @@ -67,6 +57,12 @@ editor.onClick = function() { | |||
| var found = editor.findNodeOnRay(mouse.ray); | |||
| if (found) { | |||
| editor.focus(found); | |||
| var node=found; | |||
| editor.grid.position.copy(abj.node[node].position); | |||
| gui.controls.target.copy(abj.node[node].position); | |||
| node_name.innerHTML = "Node " + node; | |||
| node_data.className = "visible"; | |||
| node_vop.innerHTML = "VOP: " + abj.node[node].vop; | |||
| } else { | |||
| var intersection = mouse.ray.intersectPlane(editor.plane); | |||
| if (intersection !== null) { | |||
| @@ -80,10 +76,10 @@ editor.onShiftClick = function() { | |||
| if (found === undefined){ return; } | |||
| if (editor.selection === undefined){ return; } | |||
| if (found === editor.selection){ return; } | |||
| abj.act_cz(found, editor.selection); | |||
| editor.focus(found); | |||
| //abj.act_cz(found, editor.selection); | |||
| websocket.edit({action:"cz", start:found, end:editor.selection}); | |||
| gui.serverMessage("Acted CZ between " + found + " & " + editor.selection + "."); | |||
| graph.update(); | |||
| editor.focus(found); | |||
| }; | |||
| editor.onCtrlClick = function() { | |||
| @@ -91,9 +87,8 @@ editor.onCtrlClick = function() { | |||
| if (found === undefined){ return; } | |||
| if (editor.selection === undefined){ return; } | |||
| editor.focus(found); | |||
| abj.act_hadamard(found); | |||
| websocket.edit({action:"hadamard", node:found}); | |||
| gui.serverMessage("Acted H on node " + found + "."); | |||
| graph.update(); | |||
| }; | |||
| @@ -147,16 +142,47 @@ editor.findNodeOnRay = function(ray) { | |||
| editor.deleteNode = function() { | |||
| if (editor.selection === undefined){ return; } | |||
| abj.del_node(editor.selection); | |||
| graph.update(); | |||
| websocket.edit({action:"delete", node:editor.selection}); | |||
| gui.serverMessage("Deleted node " + editor.selection + "."); | |||
| editor.selection = undefined; | |||
| node_data.className = "hidden"; | |||
| }; | |||
| //TODO: loadsa space for DRY here | |||
| editor.hadamard = function() { | |||
| if (editor.selection === undefined){ return; } | |||
| websocket.edit({action:"hadamard", node:editor.selection}); | |||
| gui.serverMessage("Acted Hadamard on node " + editor.selection + "."); | |||
| }; | |||
| editor.phase = function() { | |||
| if (editor.selection === undefined){ return; } | |||
| websocket.edit({action:"phase", node:editor.selection}); | |||
| gui.serverMessage("Acted phase on node " + editor.selection + "."); | |||
| }; | |||
| editor.measureX = function() { | |||
| if (editor.selection === undefined){ return; } | |||
| websocket.edit({action:"measure", node:editor.selection, basis:"x"}); | |||
| gui.serverMessage("Measured node " + editor.selection + " in X."); | |||
| }; | |||
| editor.measureY = function() { | |||
| if (editor.selection === undefined){ return; } | |||
| websocket.edit({action:"measure", node:editor.selection, basis:"y"}); | |||
| gui.serverMessage("Measured node " + editor.selection + " in Y."); | |||
| }; | |||
| editor.measureZ = function() { | |||
| if (editor.selection === undefined){ return; } | |||
| websocket.edit({action:"measure", node:editor.selection, basis:"z"}); | |||
| gui.serverMessage("Measured node " + editor.selection + " in z."); | |||
| }; | |||
| editor.localComplementation = function() { | |||
| if (editor.selection === undefined){ return; } | |||
| websocket.edit({action:"localcomplementation", node:editor.selection}); | |||
| abj.local_complementation(editor.selection); | |||
| graph.update(); | |||
| gui.serverMessage("Inverted neighbourhood of " + editor.selection + "."); | |||
| }; | |||
| @@ -47,6 +47,23 @@ graph.update = function(newState) { | |||
| edges.add(newEdge); | |||
| } | |||
| if (editor.selection) { | |||
| console.log(editor.selection); | |||
| var node = editor.selection; | |||
| if (Object.prototype.hasOwnProperty.call(abj.node, node)) { | |||
| editor.grid.position.copy(abj.node[node].position); | |||
| gui.controls.target.copy(abj.node[node].position); | |||
| node_name.innerHTML = "Node " + node; | |||
| node_data.className = "visible"; | |||
| node_vop.innerHTML = "VOP: " + abj.node[node].vop; | |||
| } else { | |||
| editor.selection = undefined; | |||
| node_data.className = "hidden"; | |||
| } | |||
| } else { | |||
| node_data.className = "hidden"; | |||
| } | |||
| var particles = new THREE.Points(geometry, materials.qubit); | |||
| var object = new THREE.Object3D(); | |||
| object.name = "graphstate"; | |||
| @@ -54,5 +71,6 @@ graph.update = function(newState) { | |||
| object.add(edges); | |||
| gui.scene.add(object); | |||
| gui.render(); | |||
| }; | |||
| @@ -1,7 +1,7 @@ | |||
| var materials = {}; | |||
| var curveProperties = { | |||
| splineDensity: 1, | |||
| splineDensity: 10, | |||
| curvature: 20 | |||
| }; | |||
| @@ -1,4 +0,0 @@ | |||
| QUnit.test( "Adding nodes", function( assert ) { | |||
| abj.add_node(0); | |||
| assert.ok(abj.node[0].vop === 10, JSON.stringify(abj.node)); | |||
| }); | |||
| @@ -1,16 +1,20 @@ | |||
| var websocket = {}; | |||
| websocket.update = undefined; | |||
| websocket.connect = function(update) { | |||
| var ws = new WebSocket("ws://localhost:5000"); | |||
| ws.onopen = function(evt) { | |||
| websocket.ws = new WebSocket("ws://localhost:5000"); | |||
| if (update){ | |||
| websocket.update = update; | |||
| } | |||
| websocket.ws.onopen = function(evt) { | |||
| gui.serverMessage("Connected to server."); | |||
| }; | |||
| ws.onerror = function(err) { | |||
| websocket.ws.onerror = function(err) { | |||
| gui.serverMessage("Could not connect to server."); | |||
| }; | |||
| ws.onmessage = function(evt) { | |||
| websocket.ws.onmessage = function(evt) { | |||
| json = JSON.parse(evt.data); | |||
| for (var i in json.node) { | |||
| var pos = json.node[i].position; | |||
| @@ -19,10 +23,14 @@ websocket.connect = function(update) { | |||
| json.node[i].vop = 0; | |||
| } | |||
| } | |||
| update(json); | |||
| websocket.update(json); | |||
| }; | |||
| ws.onclose = function(evt) { | |||
| websocket.ws.onclose = function(evt) { | |||
| gui.serverMessage("No connection to server. <a href='#' onclick='javascript:websocket.connect()'>Reconnect</a>.", true); | |||
| }; | |||
| }; | |||
| websocket.edit = function (data) { | |||
| websocket.ws.send("edit:"+JSON.stringify(data)); | |||
| }; | |||
| @@ -12,17 +12,55 @@ import os, sys, threading | |||
| import webbrowser | |||
| import argparse | |||
| import abp | |||
| import json | |||
| from pkg_resources import resource_filename | |||
| from pprint import pprint | |||
| import time | |||
| clients = [] | |||
| local_state = abp.GraphState() | |||
| def process_edit(edit, client, server): | |||
| action = edit["action"] | |||
| pprint(edit) | |||
| if action == "create": | |||
| local_state.add_qubit(edit["name"], position=edit["position"], vop=0) | |||
| elif action == "cz": | |||
| local_state.act_cz(edit["start"], edit["end"]) | |||
| elif action == "hadamard": | |||
| local_state.act_hadamard(edit["node"]) | |||
| elif action == "phase": | |||
| local_state.act_local_rotation(edit["node"], "phase") | |||
| elif action == "delete": | |||
| local_state._del_node(edit["node"]) | |||
| elif action == "localcomplementation": | |||
| local_state.local_complementation(edit["node"]) | |||
| elif action == "measure": | |||
| local_state.measure(edit["node"], "p"+edit["basis"]) | |||
| else: | |||
| pass | |||
| #server.send_message(client, json.dumps(local_state.to_json())) | |||
| server.send_message_to_all(json.dumps(local_state.to_json())) | |||
| def new_message(client, server, message): | |||
| print "Received update from client {}.".format(client["id"]) | |||
| server.send_message_to_all(message) | |||
| if message.startswith("edit:"): | |||
| edit = json.loads(message[5:]) | |||
| process_edit(edit, client, server) | |||
| else: | |||
| print "Received update from python {}.".format(client["id"]) | |||
| print message | |||
| local_state.from_json(json.loads(message)) | |||
| print local_state | |||
| server.send_message_to_all(message) | |||
| def new_client(client, server): | |||
| print "Client {} connected.".format(client["id"]) | |||
| clients.append(client) | |||
| print "Sent state of {} nodes to client {}".format(local_state.order(), client["id"]) | |||
| server.send_message(client, json.dumps(local_state.to_json())) | |||
| def client_left(client, server): | |||
| print "Client {} disconnected.".format(client["id"]) | |||
| @@ -2,13 +2,22 @@ from abp.fancy import GraphState | |||
| from abp.util import xyz | |||
| import itertools | |||
| psi = GraphState() | |||
| grid = itertools.product(range(10), range(10)) | |||
| for i, (x, y) in enumerate(grid): | |||
| psi.add_qubit(i, position=xyz(x, y, 0), vop=0) | |||
| def grid_2d(width, height): | |||
| """ Make a 2D grid """ | |||
| psi = GraphState() | |||
| grid = list(itertools.product(range(width), range(height))) | |||
| for i in range(50): | |||
| psi.act_cz(i, i+1) | |||
| for x, y in grid: | |||
| psi.add_qubit((x, y), position=xyz(x, y, 0), vop=0) | |||
| psi.update() | |||
| for x, y in grid: | |||
| if x<width-1: psi.act_cz((x, y), (x+1, y)) | |||
| if y<height-1: psi.act_cz((x, y), (x, y+1)) | |||
| return psi | |||
| if __name__ == '__main__': | |||
| psi = grid_2d(5, 5) | |||
| psi.update() | |||
| @@ -34,10 +34,11 @@ nodes, edges = lattice(square_unit_cell, (10, 10)) | |||
| psi = GraphState() | |||
| for node in nodes: | |||
| psi.add_node(str(node), position=xyz(node[0], node[1])) | |||
| psi.add_qubit(str(node), position=xyz(node[0], node[1])) | |||
| psi.act_hadamard(str(node)) | |||
| psi.update(0.1) | |||
| for edge in edges: | |||
| psi.act_cz(str(edge[0]), str(edge[1])) | |||
| psi.update(0.1) | |||
| @@ -45,9 +45,10 @@ nodes, edges = lattice(threedee_unit_cell, (3, 3, 3)) | |||
| psi = GraphState() | |||
| for node in nodes: | |||
| psi.add_node(str(node), position=xyz(*node)) | |||
| psi.add_qubit(str(node), position=xyz(*node)) | |||
| psi.act_hadamard(str(node)) | |||
| for edge in edges: | |||
| psi.act_cz(str(edge[0]), str(edge[1])) | |||
| psi.update() | |||
| @@ -131,3 +131,10 @@ def test_from_nx(): | |||
| psi = GraphState(nx.Graph(((0, 1),))) | |||
| def test_del_node(): | |||
| """ Test deleting nodes """ | |||
| g = GraphState(10) | |||
| g.act_circuit(mock.random_stabilizer_circuit()) | |||
| g._del_node(0) | |||
| assert g.order() == 9 | |||
| @@ -0,0 +1,31 @@ | |||
| import mock | |||
| import abp | |||
| def test_json(): | |||
| """ Test to_json and from_json """ | |||
| a = mock.named_node_graph() | |||
| j = a.to_json() | |||
| b = abp.GraphState() | |||
| b.from_json(j) | |||
| assert a == b | |||
| def test_json_again(): | |||
| """ Test to_json and from_json """ | |||
| # Make a random graph | |||
| a = abp.GraphState(10) | |||
| a.act_circuit(mock.random_graph_circuit()) | |||
| # Dump it to JSON | |||
| j = a.to_json() | |||
| # Reconstruct from JSON | |||
| b = abp.GraphState() | |||
| b.from_json(j) | |||
| # Check equality | |||
| assert a == b | |||