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.
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.