├── .gitignore ├── jsqis-view.css ├── tests ├── index.html └── tests.js ├── package.json ├── LICENSE ├── requestAnimationFrame-polyfill └── rAF.js ├── README.md ├── jsqis-photon-view.js ├── docs ├── features.js └── features.html ├── deck.jsqis.js ├── jsqis-core.js └── jsqis-view.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /jsqis-view.css: -------------------------------------------------------------------------------- 1 | table.QuantumBitMachineView { 2 | display: inline-table; 3 | } 4 | .QuantumBitMachineView td { 5 | text-align: center; 6 | } 7 | .QuantumBitMachineView span { 8 | padding: 0 .3em; 9 | } 10 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jsqis tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsqis", 3 | "version": "0.1.1", 4 | "description": "quantum information simulator", 5 | "author": "Jim Garrison (http://jimgarrison.org)", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/garrison/jsqis.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/garrison/jsqis/issues" 12 | }, 13 | "license": "MIT", 14 | "dependencies": { 15 | "mathjs": "^7.1.0", 16 | "seedrandom": "~2.3.10", 17 | "jquery": "^3.5.0", 18 | "qunit": "~2.11.2", 19 | "raphael": "~2.1.0", 20 | "raf": "~2.0.3" 21 | }, 22 | "keywords": [ 23 | "quantum", 24 | "physics" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | QUnit.module("assertion framework"); 2 | QUnit.test("assert passed", function (assert) { 3 | assert.equal(jsqis.assert(true), undefined, "Passed!"); 4 | }); 5 | QUnit.test("assert failed", function (assert) { 6 | assert.throws(function () { 7 | jsqis.assert(false); 8 | }, "Passed!"); 9 | }); 10 | 11 | QUnit.module("QuantumBitMachine options"); 12 | QUnit.test("default options", function (assert) { 13 | var machine = new jsqis.QuantumBitMachine(1); 14 | assert.strictEqual(machine.options.rescaleStrategy, "unity"); 15 | }); 16 | QUnit.test("override default options", function (assert) { 17 | var machine = new jsqis.QuantumBitMachine(1, {rescaleStrategy: "max"}); 18 | assert.strictEqual(machine.options.rescaleStrategy, "max"); 19 | }); 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 James R. Garrison 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /requestAnimationFrame-polyfill/rAF.js: -------------------------------------------------------------------------------- 1 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 2 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 3 | 4 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel 5 | 6 | // MIT license 7 | 8 | (function() { 9 | var lastTime = 0; 10 | var vendors = ['ms', 'moz', 'webkit', 'o']; 11 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 12 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 13 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 14 | || window[vendors[x]+'CancelRequestAnimationFrame']; 15 | } 16 | 17 | if (!window.requestAnimationFrame) 18 | window.requestAnimationFrame = function(callback, element) { 19 | var currTime = new Date().getTime(); 20 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 21 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 22 | timeToCall); 23 | lastTime = currTime + timeToCall; 24 | return id; 25 | }; 26 | 27 | if (!window.cancelAnimationFrame) 28 | window.cancelAnimationFrame = function(id) { 29 | clearTimeout(id); 30 | }; 31 | }()); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jsqis: Javascript Quantum Information Simulator 2 | =============================================== 3 | 4 | jsqis, at its core, is a [quantum 5 | computer](http://en.wikipedia.org/wiki/Quantum_computer) simulator 6 | written in Javascript. It allows initialization of quantum registers 7 | and their manipulation by means of [quantum 8 | gates](http://en.wikipedia.org/wiki/Quantum_gate). 9 | 10 | Additionally, when used in the browser, jsqis provides a lucid visual 11 | representation of a system of quantum bits. This representation is 12 | mathematically precise, allowing people to reason about quantum 13 | computing without having mastered any topics in mathematics. 14 | 15 | This package is meant for both instructional purposes and self 16 | learning through experimentation. It was originally implemented as a 17 | visual aid for a [presentation about quantum 18 | computing](http://www.meetup.com/Los-Angeles-Hacker-News/events/81455742/) 19 | given at the LA Hacker News Meetup. 20 | 21 | Install instructions 22 | -------------------- 23 | 24 | After cloning, you will need to use npm to install the various 25 | dependencies. 26 | 27 | $ git clone https://github.com/garrison/jsqis.git 28 | $ npm install 29 | 30 | There is not yet a system for building/distributing jsqis through npm, 31 | however. (Patches/gulpfiles welcome!) 32 | 33 | Getting started 34 | --------------- 35 | 36 | See docs/features.html once the dependencies are installed. 37 | 38 | Authors 39 | ------- 40 | 41 | * Jim Garrison 42 | -------------------------------------------------------------------------------- /jsqis-photon-view.js: -------------------------------------------------------------------------------- 1 | /*global math, jQuery, Raphael */ 2 | 3 | // depends: jsqis-core.js 4 | // 5 | // depends: jQuery 6 | // depends: raphael 7 | // depends: requestAnimationFrame browser support || rAF.js 8 | 9 | jQuery.extend(window.jsqis, (function ($) { 10 | 11 | var PhotonView = function (parentElement, machine, options) { 12 | this.options = $.extend({}, PhotonView.defaultOptions, options); 13 | var r1 = 50 * this.options.scale, 14 | r2 = 5 * this.options.scale; 15 | this.elt = $('').appendTo(parentElement); 16 | this.paper = Raphael(this.elt[0], 2 * r1, 2 * r1); 17 | this.paper.circle(r1, r1, r2 / 2).attr({fill: "#aaa", stroke: "none"}); 18 | this.circle = this.paper.circle(r1, r1, r2).attr({fill: "#00a", stroke: "#00e"}); 19 | this.r3 = r1 - r2; 20 | this.update(machine); 21 | this.offsetTime = 0; 22 | this.lastFrameTime = 0; 23 | this.animationInProgress = false; 24 | this.render(0); 25 | }; 26 | PhotonView.defaultOptions = { 27 | scale: 1 28 | }; 29 | PhotonView.prototype.update = function (machine) { 30 | // fixme: assert machine.nQubits == 1 31 | this.amplitude1 = math.abs(machine.amplitudeList[0]); 32 | this.amplitude2 = math.abs(machine.amplitudeList[1]); 33 | this.phase1 = math.arg(machine.amplitudeList[0]); 34 | this.phase2 = math.arg(machine.amplitudeList[1]); 35 | }; 36 | PhotonView.prototype.render = function (t) { 37 | var x = this.amplitude2 * Math.sin(t + this.phase2), 38 | y = this.amplitude1 * Math.sin(t + this.phase1); 39 | this.circle.transform("T" + (x * this.r3) + "," + (-y * this.r3)); 40 | }; 41 | PhotonView.prototype.toggleAnimation = function (status) { 42 | var self = this; 43 | var animateFrame = function (time) { 44 | if (!self.animationInProgress) { 45 | self.offsetTime += time - self.lastFrameTime; 46 | self.animationInProgress = true; 47 | } 48 | self.lastFrameTime = time; 49 | self.render((time - self.offsetTime) / 200); 50 | self.intervalID = window.requestAnimationFrame(animateFrame); 51 | }; 52 | if (status === undefined) { 53 | // if status is not given, toggle it. 54 | status = (this.intervalID === undefined); 55 | } 56 | if (status && this.intervalID === undefined) { 57 | // start animation 58 | this.intervalID = window.requestAnimationFrame(animateFrame); 59 | } else if (!status && this.intervalID !== undefined) { 60 | // stop animation 61 | window.cancelAnimationFrame(this.intervalID); 62 | this.intervalID = undefined; 63 | this.animationInProgress = false; 64 | } 65 | }; 66 | 67 | return { 68 | PhotonView: PhotonView 69 | }; 70 | 71 | })(jQuery)); 72 | -------------------------------------------------------------------------------- /docs/features.js: -------------------------------------------------------------------------------- 1 | (function ($, jsqis) { 2 | $(function () { 3 | var machine = new jsqis.QuantumBitMachine(1); 4 | new jsqis.QuantumBitMachineView($("#single-bit-state0"), machine, {color: false}); 5 | }); 6 | $(function () { 7 | var machine = new jsqis.QuantumBitMachine(1); 8 | machine.execute([[jsqis.gate.X, 0]]); 9 | new jsqis.QuantumBitMachineView($("#single-bit-state1"), machine, {color: false}); 10 | }); 11 | $(function () { 12 | var machine = new jsqis.QuantumBitMachine(1); 13 | machine.execute([[jsqis.gate.H, 0]]); 14 | new jsqis.QuantumBitMachineView($("#single-qubit-superposition"), machine, {color: false}); 15 | }); 16 | $(function () { 17 | var machine = new jsqis.QuantumBitMachine(1); 18 | machine.execute([[jsqis.gate.H, 0], [jsqis.gate.T, 0], [jsqis.gate.T, 0], [jsqis.gate.T, 0]]); 19 | new jsqis.QuantumBitMachineView($("#single-qubit-relative-phase"), machine, {color: false}); 20 | }); 21 | $(function () { 22 | var machine = new jsqis.QuantumBitMachine(1); 23 | machine.execute([[jsqis.gate.H, 0], [jsqis.gate.T, 0], [jsqis.gate.H, 0]]); 24 | new jsqis.QuantumBitMachineView($("#single-qubit-phase-amplitude"), machine, {color: false}); 25 | new jsqis.QuantumBitMachineView($("#single-qubit-phase-amplitude-color"), machine, {color: true}); 26 | }); 27 | $(function () { 28 | var machine = new jsqis.QuantumBitMachine(1); 29 | machine.execute([[jsqis.gate.H, 0], [jsqis.gate.T, 0], [jsqis.gate.T, 0], [jsqis.gate.T, 0], [jsqis.gate.H, 0]]); 30 | new jsqis.QuantumBitMachineView($("#invariant-state-1"), machine); 31 | machine.execute([[jsqis.gate.globalPhase, .25], [jsqis.gate.rescale, .8]]); 32 | new jsqis.QuantumBitMachineView($("#invariant-state-2"), machine); 33 | machine.execute([[jsqis.gate.globalPhase, .4], [jsqis.gate.rescale, .6]]); 34 | new jsqis.QuantumBitMachineView($("#invariant-state-3"), machine); 35 | machine.execute([[jsqis.gate.globalPhase, .5], [jsqis.gate.rescale, 1]]); 36 | new jsqis.QuantumBitMachineView($("#invariant-state-4"), machine); 37 | }); 38 | $(function () { 39 | new jsqis.QuantumCircuitView($("#circuit-1"), 4, [[jsqis.gate.X, 0], [jsqis.gate.Z, 2], [jsqis.gate.X, 1], [jsqis.gate.CCNOT, 0, 1, 2], [jsqis.gate.randomize, 333], [jsqis.gate.measure, 253, 3]]); 40 | }); 41 | $(function () { 42 | function setupPhoton (elt, commandList) { 43 | var pv, machine = new jsqis.QuantumBitMachine(1), 44 | span = $("").appendTo(elt); 45 | if (commandList) { 46 | machine.execute(commandList); 47 | } 48 | pv = new jsqis.PhotonView(span, machine); 49 | new jsqis.QuantumBitMachineView(span, machine); 50 | span.hover(function () { 51 | pv.toggleAnimation(true); 52 | }, function () { 53 | pv.toggleAnimation(false); 54 | }); 55 | } 56 | setupPhoton($("#photon-polarization1")); 57 | setupPhoton($("#photon-polarization2"), [[jsqis.gate.X, 0]]); 58 | setupPhoton($("#photon-polarization3"), [[jsqis.gate.H, 0]]); 59 | setupPhoton($("#photon-polarization4"), [[jsqis.gate.H, 0], [jsqis.gate.Z, 0]]); 60 | setupPhoton($("#photon-polarization5"), [[jsqis.gate.H, 0], [jsqis.gate.T, 0], [jsqis.gate.T, 0]]); 61 | setupPhoton($("#photon-polarization6"), [[jsqis.gate.H, 0], [jsqis.gate.Z, 0], [jsqis.gate.T, 0], [jsqis.gate.T, 0]]); 62 | setupPhoton($("#photon-polarization7"), [[jsqis.gate.H, 0], [jsqis.gate.Z, 0], [jsqis.gate.T, 0]]); 63 | setupPhoton($("#photon-polarization8"), [[jsqis.gate.randomize, 2]]); 64 | setupPhoton($("#photon-polarization9"), [[jsqis.gate.randomize, 1]]); 65 | setupPhoton($("#photon-polarization10"), [[jsqis.gate.randomize, 0]]); 66 | }); 67 | })(jQuery, jsqis); 68 | -------------------------------------------------------------------------------- /deck.jsqis.js: -------------------------------------------------------------------------------- 1 | // depends: jsqis-core.js 2 | // jsqis-view.js 3 | 4 | // fixme: make it so more than one machine can be shown on a given slide 5 | 6 | (function ($, jsqis) { 7 | // initialize QuantumBitMachineView's 8 | $(".jsqis-machine").each(function () { 9 | var machine = new jsqis.QuantumBitMachine($(this).data("qubits"), eval("(" + $(this).data("options") + ")")), 10 | machineView = new jsqis.QuantumBitMachineView(this, machine, eval("(" + $(this).data("view-options") + ")")), 11 | circuitViewContainer = $(this).data("circuit-view-container"), 12 | circuitView = null, 13 | photonViewContainer = $(this).data("photon-view-container"), 14 | photonView = null, 15 | operations, 16 | circuitOperations = [], 17 | gate = jsqis.gate; 18 | //// $(elt).data("machine", machine).data("machineView", machineView); 19 | 20 | // find all qubit operations 21 | // 22 | // http://stackoverflow.com/questions/8771463/jquery-find-what-order-does-it-return-elements-in 23 | // 24 | // NOTE: "As of jQuery 1.4 the results from .add() will always 25 | // be returned in document order (rather than a simple 26 | // concatenation)." (c.f. http://api.jquery.com/add/), so 27 | // machineSlide's operations are guaranteed to execute before 28 | // others in the below code. 29 | var machineSlide = $(this).parents('.slide').one(); 30 | machineSlide.find('.slide').add(machineSlide).each(function () { 31 | if ($(this).hasClass("jsqis-gate")) { 32 | // determine the current operations 33 | with (gate) { 34 | operations = eval($(this).data("operations")); 35 | } 36 | // determine the state after executing the current operations 37 | machine = machine.copy(); 38 | machine.execute(operations); 39 | // determine which operations should be passed to the circuit diagram 40 | if (machineSlide[0] != $(this)[0]) { // don't display initialization operations in the circuit diagram 41 | for (var i = 0; i < operations.length; ++i) { 42 | // filter out no-op operations (rescale and globalPhase) before passing to circuit diagram 43 | if (jsqis.gateRenderer[operations[i][0].name]) { 44 | circuitOperations.push(operations[i]); 45 | } 46 | } 47 | } 48 | } 49 | $(this).data({ 50 | machineState: machine, 51 | nOperations: circuitOperations.length, 52 | machineSlide: machineSlide 53 | }); 54 | }).bind('deck.becameCurrent', function (event) { 55 | // update the machine view's state 56 | machineView.update($(this).data("machineState")); 57 | // update the marker in the circuit view (if necessary) 58 | if (circuitView) { 59 | circuitView.update($(this).data("nOperations")); 60 | } 61 | // update the photon polarization and begin animation (if necessary) 62 | if (photonView) { 63 | photonView.update($(this).data("machineState")); 64 | photonView.toggleAnimation(true); 65 | } 66 | // fixme: we really want the event to bubble, but not call 67 | // this same function again on the machineSlide ... 68 | event.stopPropagation(); 69 | }).bind('deck.lostCurrent', function (event) { 70 | // we do this using setTimeout because $.deck('getSlide') is not updated with the current slide until /after/ the deck.lostCurrent event is triggered. 71 | window.setTimeout(function () { 72 | if (photonView && machineSlide !== $.deck('getSlide').data('machineSlide')) { 73 | photonView.toggleAnimation(false); 74 | } 75 | }, 1); 76 | }); 77 | 78 | // create a QuantumCircuitView if appropriate 79 | if (circuitViewContainer) { 80 | circuitView = new jsqis.QuantumCircuitView($(circuitViewContainer), machine.nQubits, circuitOperations, eval("(" + $(this).data("circuit-view-options") + ")")); 81 | } 82 | 83 | // create a PhotonView if appropriate 84 | if (photonViewContainer) { 85 | photonView = new jsqis.PhotonView($(photonViewContainer), machine, eval("(" + $(this).data("photon-view-options") + ")")); 86 | } 87 | }); 88 | })(jQuery, jsqis); 89 | // http://imakewebthings.com/deck.js/docs/#deck-core-theming 90 | -------------------------------------------------------------------------------- /docs/features.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jsqis: features 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

jsqis: features

19 | 20 |

jsqis, at its core, is a quantum computer simulator written in Javascript. It allows initialization of quantum registers and their manipulation by means of quantum gates.

21 | 22 |

Additionally, when used in the browser, jsqis provides a lucid visual representation of a system of quantum bits (qubits). This representation is mathematically precise, allowing people to reason about quantum computing without having mastered any topics in mathematics.

23 | 24 |

In the language of mathematics, we do this by enumerating all the computational basis states and, beside each basis state, displaying its amplitude (which is a complex number) using a simple graphical representation.

25 | 26 |

Single qubit

27 | 28 |

A single bit can be in either of two classical states, zero or one.

29 | 30 |
31 |
32 | 33 |

A quantum bit can also be in an equal superposition of those two states.

34 | 35 |
36 | 37 |

Not only that, but the states can have a relative phase difference.

38 | 39 |
40 | 41 |

And they can of course have different amplitudes as well.

42 | 43 |
44 | 45 |

When a quantum bit is measured, it will assume a definite value (either zero or one). The probability that the bit will be measured in a given state is proportional to the area of the corresponding box.

46 | 47 |

In addition to the arrow representing the relative phase, we color code it as well, choosing a continuous hue based on an amplitude's phase. This is redundant (as the arrow already encodes the full information), but stresses the fact that phase information is incredibly important in quantum mechanics.

48 | 49 |
50 | 51 |

Just as in the mathematical representation of quantum mechanics, our states are invariant up to a change in overall magnitude and global phase. In other words, it is the relative difference between the sizes and the directions of the arrows that matter. For instance, the following states are all equivalent.

52 | 53 |
54 |
55 |
56 |
57 | 58 | 59 | 60 |

Multiple qubits

61 | 62 | 65 | 66 |

Operations

67 | 68 |

Single qubit gates

69 | 70 |

Multiple qubit gates

71 | 72 |

Other operations

73 | 74 |

Randomize

75 | 76 |

Rescale

77 | 78 | 79 | 80 |

Measure

81 | 82 | 83 | 84 |

Quantum circuit diagrams

85 | 86 |
87 | 88 |

Physical realizations

89 | 90 |

Photon polarization

91 | 92 |

The various states of a single qubit can be understood in terms of photon polarization. Unfortunately, these animations can take quite a bit of CPU time, so this page only enables them when you hover the mouse over a given element.

93 | 94 |

We define the states 0 and 1 to be represented by vertically- and horizontally-polarized photons, respectively.

95 | 96 |
97 |
98 | 99 |

A an equal weighted superposition can then be in many different states. If the phases point in parallel directions, we will get diagonal polarization.

100 | 101 |
102 |
103 | 104 |

Other possibilities include left and right circular polarization.

105 | 106 |
107 |
108 | 109 |

Elliptical polarization is also possible.

110 | 111 |
112 | 113 |

Any other one-qubit quantum state can be represented this way as well.

114 | 115 |
116 |
117 |
118 | 119 |

Planned features

120 | 121 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /jsqis-core.js: -------------------------------------------------------------------------------- 1 | /*global math, _, jQuery */ 2 | 3 | // depends: mathjs 4 | // depends: jQuery or underscore.js (very lightly) 5 | // depends: seedrandom.js (optionally, for reproducable simulations) 6 | 7 | window.jsqis = (function () { 8 | 9 | var extend = jQuery ? jQuery.extend : _.extend; 10 | 11 | function AssertionError(message) { 12 | this.message = message; 13 | } 14 | AssertionError.prototype.toString = function () { 15 | return 'AssertionError: ' + this.message; 16 | }; 17 | 18 | function assert (condition, message) { 19 | if (!condition) { 20 | throw new AssertionError(message || "unspecified error"); 21 | } 22 | } 23 | 24 | var invSqrtTwo = Math.sqrt(2) / 2; 25 | var negativeInvSqrtTwo = -Math.sqrt(2) / 2; 26 | var complexPhasePiOverFour = math.complex({r: 1, phi: Math.PI / 4}); 27 | var complexPhasePiOverEight = math.complex({r: 1, phi: Math.PI / 8}); 28 | var complexPhaseNegativePiOverEight = math.complex({r: 1, phi: -Math.PI / 8}); 29 | 30 | var FundamentalQubitGate = function (nQubits, mapFunc) { 31 | this.nQubits = nQubits; 32 | this.mapFunc = mapFunc; 33 | }; 34 | 35 | var gate = {}; 36 | 37 | function registerGate (name, gateObject) { 38 | gateObject.name = name; 39 | gate[name] = gateObject; 40 | } 41 | 42 | registerGate("X", new FundamentalQubitGate(1, function (basisState, register) { 43 | return [ 44 | [basisState ^ (1 << register), 1] 45 | ]; 46 | })); 47 | 48 | registerGate("Z", new FundamentalQubitGate(1, function (basisState, register) { 49 | return [ 50 | [basisState, basisState & (1 << register) ? -1 : 1] 51 | // [basisState, basisState & (1 << register) ? math.complex(0, 1) : math.complex(0, -1)] 52 | ]; 53 | })); 54 | 55 | registerGate("T", new FundamentalQubitGate(1, function (basisState, register) { 56 | return [ 57 | [basisState, basisState & (1 << register) ? complexPhasePiOverFour : 1] 58 | // [basisState, basisState & (1 << register) ? complexPhasePiOverEight : complexPhaseNegativePiOverEight] 59 | ]; 60 | })); 61 | 62 | registerGate("Rtheta", new FundamentalQubitGate(1, function (basisState, register, arg) { 63 | var phase = math.complex({r: 1, phi: arg}); 64 | return [ 65 | [basisState, basisState & (1 << register) ? phase : 1] 66 | ]; 67 | })); 68 | 69 | registerGate("H", new FundamentalQubitGate(1, function (basisState, register) { 70 | return [ 71 | [basisState, basisState & (1 << register) ? negativeInvSqrtTwo : invSqrtTwo], 72 | [basisState ^ (1 << register), invSqrtTwo] 73 | ]; 74 | })); 75 | 76 | registerGate("CNOT", new FundamentalQubitGate(2, function (basisState, controlRegister, targetRegister) { 77 | return [ 78 | [basisState & (1 << controlRegister) ? basisState ^ (1 << targetRegister) : basisState, 1] 79 | ]; 80 | })); 81 | 82 | registerGate("CCNOT", new FundamentalQubitGate(3, function (basisState, controlRegister1, controlRegister2, targetRegister) { 83 | return [ 84 | [(basisState & (1 << controlRegister1)) && (basisState & (1 << controlRegister2)) ? basisState ^ (1 << targetRegister) : basisState, 1] 85 | ]; 86 | })); 87 | 88 | registerGate("globalPhase", new FundamentalQubitGate(0, function (basisState, arg) { 89 | var phase = math.complex({r: 1, phi: arg}); 90 | return [ 91 | [basisState, phase] 92 | ]; 93 | })); 94 | 95 | // These are not actually gates, but instead special operations 96 | registerGate("randomize", {randomize: true}); 97 | registerGate("measure", {measure: true}); 98 | registerGate("rescale", {rescale: true}); 99 | 100 | /** 101 | * Represents a quantum register with some number of qubits 102 | */ 103 | var QuantumBitMachine = function (nQubits, options) { 104 | assert(nQubits > 0 && nQubits < 24); 105 | this.nQubits = nQubits; 106 | this.options = extend({}, QuantumBitMachine.defaultOptions, options); 107 | var nBasisStates = 1 << nQubits; 108 | this.amplitudeList = [1]; 109 | for (var i = 1; i < nBasisStates; ++i) { 110 | this.amplitudeList.push(0); 111 | } 112 | }; 113 | QuantumBitMachine.defaultOptions = { 114 | /** 115 | * Available choices for rescaleStrategy: 116 | * 117 | * * 'unity': rescales such that the total probability is one 118 | * 119 | * * 'max': rescales such that the largest amplitude has a 120 | * magnitude of one (which is often necessary for 121 | * effectively visualizing a many-qubit state with much 122 | * superposition 123 | * 124 | * * a number in the range (0, 1]: rescales such that the 125 | * total "probability" is the given value 126 | * 127 | * * false or undefined: performs no rescaling 128 | */ 129 | rescaleStrategy: 'unity' 130 | }; 131 | QuantumBitMachine.prototype.copy = function () { 132 | // http://stackoverflow.com/a/122704/1558890 133 | return extend({}, this); 134 | }; 135 | QuantumBitMachine.prototype.executeCommand = function (command) { 136 | var i, j, k, newAmplitudeList = [], rng; 137 | // fixme: assert list 138 | assert(command.length > 0); 139 | assert(command[0] !== undefined); 140 | if (command[0].mapFunc) { 141 | // it's a FundamentalQubitGate 142 | // fixme: assert command.length == command[0].nQubits + 1; EXCEPT in the case of globalPhase (where nQubits == 0) 143 | // fixme: assert each register is specified no more than once 144 | for (j = 0; j < this.amplitudeList.length; ++j) { 145 | newAmplitudeList.push(0); 146 | } 147 | for (i = 0; i < this.amplitudeList.length; ++i) { 148 | if (!math.equal(this.amplitudeList[i], 0)) { 149 | var output = command[0].mapFunc.apply(null, [i].concat(command.slice(1))); 150 | for (k = 0; k < output.length; ++k) { 151 | var bs = output[k][0], amp = output[k][1]; 152 | // fixme: assert bs < this.amplitudeList.length 153 | newAmplitudeList[bs] = math.add(newAmplitudeList[bs], math.multiply(amp, this.amplitudeList[i])); 154 | } 155 | } 156 | } 157 | } else if (command[0].measure) { 158 | // it's a measurement 159 | // fixme: assert each register is correct and none is specified more than once 160 | // FIXME XXX: actually compute probabilities 161 | if (Math.seedrandom) 162 | Math.seedrandom(command[1]); 163 | var registers = command.slice(2), 164 | target = []; 165 | for (j = 0; j < registers.length; ++j) { 166 | target.push((Math.random() < .5) ? 0 : 1); 167 | } 168 | for (i = 0; i < this.amplitudeList.length; ++i) { 169 | var newAmplitude = this.amplitudeList[i]; 170 | for (j = 0; j < registers.length; ++j) { 171 | if ((i & (1 << registers[j])) != (target[j] << registers[j])) { 172 | newAmplitude = 0; 173 | break; 174 | } 175 | } 176 | newAmplitudeList.push(newAmplitude); 177 | } 178 | } else if (command[0].randomize) { 179 | // it's a randomize operation 180 | // fixme: assert there's just one argument (the seed) 181 | if (Math.seedrandom) 182 | Math.seedrandom(command[1]); 183 | for (j = 0; j < this.amplitudeList.length; ++j) { 184 | newAmplitudeList.push(math.complex({r: Math.random(), phi: 2 * Math.PI * Math.random()})); 185 | } 186 | } else if (command[0].rescale) { 187 | // we don't actually do anything, but we still need to set newAmplitudeList 188 | newAmplitudeList = this.amplitudeList.slice(0); 189 | } else { 190 | assert(false, "invalid command"); 191 | } 192 | 193 | assert(newAmplitudeList.length == this.amplitudeList.length); 194 | 195 | // determine rescaleStrategy 196 | var rescaleStrategy = undefined; 197 | if (command[0].rescale) { 198 | rescaleStrategy = command[1] || 'unity'; 199 | } else if (this.options.rescaleStrategy && (command[0].measure || command[0].randomize)) { 200 | rescaleStrategy = this.options.rescaleStrategy; 201 | } 202 | 203 | // perform any rescale strategy 204 | if (rescaleStrategy) { 205 | if (rescaleStrategy == "unity") { 206 | rescaleStrategy = 1; 207 | } 208 | var rescaleInverse = 0; 209 | if (typeof rescaleStrategy === 'number') { 210 | var psiMagnitudeSquared = 0; 211 | for (j = 0; j < newAmplitudeList.length; ++j) { 212 | psiMagnitudeSquared += math.square(math.abs(newAmplitudeList[j])); 213 | } 214 | rescaleInverse = Math.sqrt(psiMagnitudeSquared) / rescaleStrategy; 215 | } else if (rescaleStrategy == 'max') { 216 | for (j = 0; j < newAmplitudeList.length; ++j) { 217 | var m = math.abs(newAmplitudeList[j]); 218 | if (m > rescaleInverse) { 219 | rescaleInverse = m; 220 | } 221 | } 222 | } else { 223 | // invalid rescale strategy 224 | } 225 | for (j = 0; j < newAmplitudeList.length; ++j) { 226 | newAmplitudeList[j] = math.divide(newAmplitudeList[j], rescaleInverse); 227 | } 228 | } 229 | this.amplitudeList = newAmplitudeList; 230 | }; 231 | QuantumBitMachine.prototype.execute = function (commandList) { 232 | // fixme: assert list 233 | for (var i = 0; i < commandList.length; ++i) { 234 | this.executeCommand(commandList[i]); 235 | } 236 | }; 237 | QuantumBitMachine.prototype.getState = function () { 238 | // copy array (http://my.opera.com/GreyWyvern/blog/show.dml/1725165) 239 | return this.amplitudeList.slice(0); 240 | }; 241 | 242 | return { 243 | assert: assert, 244 | FundamentalQubitGate: FundamentalQubitGate, 245 | gate: gate, 246 | QuantumBitMachine: QuantumBitMachine 247 | }; 248 | 249 | })(); 250 | -------------------------------------------------------------------------------- /jsqis-view.js: -------------------------------------------------------------------------------- 1 | /*global math, jQuery, Raphael */ 2 | 3 | // depends: jsqis-core.js 4 | // 5 | // depends: jQuery 6 | // depends: raphael 7 | 8 | jQuery.extend(window.jsqis, (function ($) { 9 | 10 | // we use the cielab color space so each hue has the same perceived intensity. 11 | function cielchToRGB (l, c, h) { 12 | // l and c go from 0 to 100 13 | // h is an angle in radians 14 | 15 | // based on http://www.easyrgb.com/index.php?X=MATH 16 | 17 | // convert to XYZ 18 | var y = (l + 16) / 116; 19 | var x = c * Math.cos(h) / 500 + y; 20 | var z = y - c * Math.sin(h) / 200; 21 | y = (y > 0.206893 ? y * y * y : (y - 16 / 116) / 7.787); 22 | x = (x > 0.206893 ? x * x * x : (x - 16 / 116) / 7.787) * 0.95047; 23 | z = (z > 0.206893 ? z * z * z : (z - 16 / 116) / 7.787) * 1.08883; 24 | 25 | // convert to RGB 26 | var r = x * 3.2406 + y * -1.5372 + z * -0.4986; 27 | var g = x * -0.9689 + y * 1.8758 + z * 0.0415; 28 | var b = x * 0.0557 + y * -0.2040 + z * 1.0570; 29 | r = r > 0.0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - 0.055 : 12.92 * r; 30 | g = g > 0.0031308 ? 1.055 * Math.pow(g, 1 / 2.4) - 0.055 : 12.92 * g; 31 | b = b > 0.0031308 ? 1.055 * Math.pow(b, 1 / 2.4) - 0.055 : 12.92 * b; 32 | 33 | var m = function (v) { 34 | // ensure it's in bounds and scale it to be between 0 and 255 35 | return 255 * Math.max(Math.min(1, v), 0); 36 | }; 37 | return [m(r), m(g), m(b)]; 38 | } 39 | 40 | // modulus that works with negative numbers. see 41 | // http://stackoverflow.com/questions/4467539/javascript-modulo-not-behaving 42 | function safeModulus (a, b) { 43 | return ((a % b) + b) % b; 44 | } 45 | 46 | // only an amplitude within the unit circle is guaranteed to fit 47 | // on the paper 48 | var AmplitudeView = function (initialAmplitude, options) { 49 | this.options = $.extend({}, AmplitudeView.defaultOptions, options); 50 | 51 | this.elt = $(""); 52 | var scale = this.options.scale, 53 | paper = Raphael(this.elt[0], 74 * scale, 74 * scale); 54 | 55 | this.box = paper.rect(12 * scale, 12 * scale, 50 * scale, 50 * scale); 56 | this.arrow = paper.path("M" + (37 * scale) + "," + (62 * scale) + "v" + (-43 * scale) + "l" + (-3 * scale) + ",0l" + (3 * scale) + "," + (-6 * scale) + "l" + (3 * scale) + "," + (6 * scale) + "h" + (-3 * scale) + "z"); 57 | this.obj = paper.set().push(this.box, this.arrow); 58 | 59 | this.obj.transform(this.calculateTransform(initialAmplitude)); 60 | this.box.attr(this.calculateBoxColor(initialAmplitude)); 61 | this.arrow.animate(this.calculateArrowColor(initialAmplitude)); 62 | }; 63 | AmplitudeView.defaultOptions = { 64 | scale: 1, 65 | color: true, 66 | displayBox: true 67 | }; 68 | AmplitudeView.prototype.calculateTransform = function (amplitude) { 69 | // returns Raphael "transform" string from amplitude 70 | var oldRotation = this.currentRotation || 0; 71 | // we want to rotate in the direction that gets us to our destination most directly 72 | this.currentRotation = oldRotation + safeModulus(Raphael.deg(math.arg(amplitude)) + 180 - oldRotation, 360) - 180; 73 | return "S" + math.abs(amplitude) + "R" + (-this.currentRotation); 74 | }; 75 | AmplitudeView.prototype.calculateBoxColor = function (amplitude) { 76 | if (!this.options.displayBox) { 77 | return {fill: "rgba(0, 0, 0, 0)", stroke: "rgba(0, 0, 0, 0)"}; 78 | } 79 | var arg = math.arg(amplitude), 80 | color = this.options.color; 81 | return {fill: "rgba(" + cielchToRGB(80, color ? 35 : 0, arg).join(",") + ", .8)", stroke: "rgba(" + cielchToRGB(50, color ? 25 : 0, arg).join(",") + ", .8)"}; 82 | }; 83 | AmplitudeView.prototype.calculateArrowColor = function (amplitude) { 84 | var arg = math.arg(amplitude), 85 | color = this.options.color; 86 | return {fill: "rgb(" + cielchToRGB(55, color ? 25 : 0, arg).join(",") + ")", stroke: "rgb(" + cielchToRGB(35, color ? 15 : 0, arg).join(",") + ")"}; 87 | }; 88 | AmplitudeView.prototype.update = function (amplitude) { 89 | // fixme: only do transform if something actually changed 90 | this.obj.animate({transform: this.calculateTransform(amplitude)}, 200, "back-out"); 91 | this.box.animate(this.calculateBoxColor(amplitude), 200, "back-out"); 92 | this.arrow.animate(this.calculateArrowColor(amplitude), 200, "back-out"); 93 | }; 94 | 95 | function zeroFillBinary (number, width) { 96 | // returns a number's binary representation. zeroFillBinary(5, 4) == 0101 97 | number = number.toString(2); 98 | var pad_length = width - number.length; 99 | var pad = ""; 100 | while (pad_length--) { 101 | pad += "0"; 102 | } 103 | return pad + number; 104 | }; 105 | 106 | var QuantumBitMachineView = function (parentElement, machine, options) { 107 | this.machine = machine; 108 | this.amplitudeViewList = []; 109 | this.options = $.extend({}, QuantumBitMachineView.defaultOptions, options); 110 | var i, amplitudeViewOptions = { 111 | color: this.options.color, 112 | scale: this.options.scale, 113 | displayBox: this.options.displayBox 114 | }, tr, td, table = $('
'); 115 | for (i = 0; i < machine.amplitudeList.length; ++i) { 116 | if (i % this.options.amplitudesPerRow == 0) 117 | tr = $("").appendTo(table); 118 | td = $("").appendTo(tr); 119 | var amplitudeView = new AmplitudeView(machine.amplitudeList[i], amplitudeViewOptions); 120 | this.amplitudeViewList.push(amplitudeView); 121 | amplitudeView.elt.appendTo(td); 122 | td.append("
"); 123 | $('').text(zeroFillBinary(i, machine.nQubits)).appendTo(td); 124 | } 125 | table.appendTo(parentElement); 126 | }; 127 | QuantumBitMachineView.defaultOptions = { 128 | amplitudesPerRow: 8, 129 | // these options, if provided, are passed to each AmplitudeView 130 | color: undefined, 131 | scale: undefined, 132 | displayBox: undefined 133 | }; 134 | QuantumBitMachineView.prototype.update = function (machine) { 135 | if (machine) { 136 | this.machine = machine; 137 | } 138 | 139 | // fixme: assert this.machine.amplitudeList.length == this.amplitudeViewList.length; 140 | for (var i = 0; i < this.amplitudeViewList.length; ++i) { 141 | this.amplitudeViewList[i].update(this.machine.amplitudeList[i]); 142 | } 143 | }; 144 | 145 | var QuantumCircuitView = function (parentElement, nQubits, operations, options) { 146 | // fixme: make operations modifiable (or make it reference a 147 | // machine, and have an update() method that also calls the 148 | // machine) 149 | this.options = $.extend({}, QuantumCircuitView.defaultOptions, options); 150 | this.elt = $('').appendTo(parentElement); 151 | var i, x, y, 152 | margin = 10 * this.options.scale, 153 | gridSize = this.options.scale * 64 + 2 * this.options.extraPadding, 154 | headerHeight = gridSize * 1.5, 155 | markerHeight = 10 * this.options.scale, 156 | markerOverflow = 6 * this.options.scale, 157 | paper = Raphael(this.elt[0], gridSize * nQubits + 1 + 2 * margin, headerHeight + gridSize * operations.length + 1 + 2 * margin + markerHeight / 2); 158 | 159 | // header 160 | paper.rect(margin, margin, nQubits * gridSize, headerHeight).attr({fill: "rgba(200, 200, 200, .6)", stroke: "none"}); 161 | x = (nQubits * gridSize) / 2 + margin; 162 | y = gridSize / 2 + margin; 163 | paper.text(x, y, nQubits + " " + (nQubits == 1 ? "bit" : "bits")).attr({'font-size': 24 * this.options.scale}); 164 | // horizontal lines 165 | x = nQubits * gridSize + margin; 166 | for (i = 0; i < operations.length + 1; ++i) { 167 | y = i * gridSize + headerHeight + margin; 168 | paper.path("M" + margin + "," + y + " L" + x + "," + y); 169 | } 170 | paper.path("M" + margin + "," + margin + " L" + x + "," + margin); 171 | // vertical lines 172 | y = operations.length * gridSize + headerHeight + margin; 173 | for (i = 0; i < nQubits + 1; ++i) { 174 | x = i * gridSize + margin; 175 | paper.path("M" + x + "," + ((i % nQubits == 0 ? 0 : headerHeight) + margin) + " L" + x + "," + y); 176 | } 177 | 178 | // display the relevant operation in each cell 179 | for (var j = 0; j < operations.length; ++j) { 180 | y = headerHeight + gridSize * (j + .5) + margin; 181 | var calculateX = function (i) { 182 | return gridSize * (nQubits - i - .5) + margin; 183 | }; 184 | gateRenderer[operations[j][0].name](paper, this.options.scale, calculateX, y, operations[j], nQubits); 185 | } 186 | 187 | // create the marker 188 | if (this.options.showMarker) { 189 | this.marker = paper.rect(margin - markerOverflow, margin + headerHeight - markerHeight / 2, gridSize * nQubits + 2 * markerOverflow, markerHeight).attr({fill: '#ff0'}); 190 | } 191 | }; 192 | QuantumCircuitView.prototype.update = function (operationsComplete) { 193 | // fixme: gridSize is defined above 194 | var gridSize = this.options.scale * 64 + 2 * this.options.extraPadding; 195 | if (this.options.showMarker) { 196 | this.marker.animate({transform: 'T0,' + (operationsComplete * gridSize)}, 100); 197 | } 198 | }; 199 | QuantumCircuitView.defaultOptions = { 200 | showMarker: true, 201 | scale: 1, 202 | extraPadding: 0 203 | }; 204 | 205 | var gateRenderer = { 206 | X: function (paper, scale, calculateX, y, args) { 207 | paper.text(calculateX(args[1]), y, 'X').attr({'font-size': 32 * scale, 'fill': '#a00'}); 208 | }, 209 | Z: function (paper, scale, calculateX, y, args) { 210 | paper.text(calculateX(args[1]), y, 'Z').attr({'font-size': 32 * scale, 'fill': '#a00'}); 211 | }, 212 | T: function (paper, scale, calculateX, y, args) { 213 | paper.text(calculateX(args[1]), y, 'T').attr({'font-size': 32 * scale, 'fill': '#a00'}); 214 | }, 215 | H: function (paper, scale, calculateX, y, args) { 216 | paper.text(calculateX(args[1]), y, 'H').attr({'font-size': 32 * scale, 'fill': '#a00'}); 217 | }, 218 | CNOT: function (paper, scale, calculateX, y, args) { 219 | paper.circle(calculateX(args[1]), y, 6 * scale).attr({'fill': '#00a'}); 220 | paper.text(calculateX(args[2]), y, 'X').attr({'font-size': 32 * scale, 'fill': '#a00'}); 221 | }, 222 | CCNOT: function (paper, scale, calculateX, y, args) { 223 | paper.circle(calculateX(args[1]), y, 6 * scale).attr({'fill': '#00a'}); 224 | paper.circle(calculateX(args[2]), y, 6 * scale).attr({'fill': '#00a'}); 225 | paper.text(calculateX(args[3]), y, 'X').attr({'font-size': 32 * scale, 'fill': '#a00'}); 226 | }, 227 | randomize: function (paper, scale, calculateX, y, args, nQubits) { 228 | for (var i = 0; i < nQubits; ++i) { 229 | paper.text(calculateX(i), y, '?').attr({'font-size': 32 * scale, 'fill': '#a00'}); 230 | } 231 | }, 232 | measure: function (paper, scale, calculateX, y, args) { 233 | for (var i = 2; i < args.length; ++i) { 234 | // icon from http://raphaeljs.com/icons/ (MIT license, Copyright 2008 Dmitry Baranovskiy) 235 | var measurement_icon = "M29.772,26.433l-7.126-7.126c0.96-1.583,1.523-3.435,1.524-5.421C24.169,8.093,19.478,3.401,13.688,3.399C7.897,3.401,3.204,8.093,3.204,13.885c0,5.789,4.693,10.481,10.484,10.481c1.987,0,3.839-0.563,5.422-1.523l7.128,7.127L29.772,26.433zM7.203,13.885c0.006-3.582,2.903-6.478,6.484-6.486c3.579,0.008,6.478,2.904,6.484,6.486c-0.007,3.58-2.905,6.476-6.484,6.484C10.106,20.361,7.209,17.465,7.203,13.885z"; 236 | paper.path(measurement_icon).attr({fill: "#090", stroke: "none"}).transform("S" + scale + "T" + (calculateX(args[i]) - 16) + "," + (y - 16)); 237 | } 238 | }, 239 | // these operations should never be passed to a circuit diagram (they should be filtered out first) 240 | globalPhase: null, 241 | rescale: null 242 | }; 243 | 244 | return { 245 | AmplitudeView: AmplitudeView, 246 | QuantumBitMachineView: QuantumBitMachineView, 247 | QuantumCircuitView: QuantumCircuitView, 248 | gateRenderer: gateRenderer 249 | }; 250 | 251 | })(jQuery)); 252 | --------------------------------------------------------------------------------