Skip to content

Commit ee34a44

Browse files
committedJan 15, 2017
Quantum Simulator
1 parent 1eb571a commit ee34a44

20 files changed

+3034
-1
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

‎LICENSE

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
Apache License
2+
Version 2.0, January 2004
3+
http://www.apache.org/licenses/
4+
5+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6+
7+
1. Definitions.
8+
9+
"License" shall mean the terms and conditions for use, reproduction,
10+
and distribution as defined by Sections 1 through 9 of this document.
11+
12+
"Licensor" shall mean the copyright owner or entity authorized by
13+
the copyright owner that is granting the License.
14+
15+
"Legal Entity" shall mean the union of the acting entity and all
16+
other entities that control, are controlled by, or are under common
17+
control with that entity. For the purposes of this definition,
18+
"control" means (i) the power, direct or indirect, to cause the
19+
direction or management of such entity, whether by contract or
20+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
21+
outstanding shares, or (iii) beneficial ownership of such entity.
22+
23+
"You" (or "Your") shall mean an individual or Legal Entity
24+
exercising permissions granted by this License.
25+
26+
"Source" form shall mean the preferred form for making modifications,
27+
including but not limited to software source code, documentation
28+
source, and configuration files.
29+
30+
"Object" form shall mean any form resulting from mechanical
31+
transformation or translation of a Source form, including but
32+
not limited to compiled object code, generated documentation,
33+
and conversions to other media types.
34+
35+
"Work" shall mean the work of authorship, whether in Source or
36+
Object form, made available under the License, as indicated by a
37+
copyright notice that is included in or attached to the work
38+
(an example is provided in the Appendix below).
39+
40+
"Derivative Works" shall mean any work, whether in Source or Object
41+
form, that is based on (or derived from) the Work and for which the
42+
editorial revisions, annotations, elaborations, or other modifications
43+
represent, as a whole, an original work of authorship. For the purposes
44+
of this License, Derivative Works shall not include works that remain
45+
separable from, or merely link (or bind by name) to the interfaces of,
46+
the Work and Derivative Works thereof.
47+
48+
"Contribution" shall mean any work of authorship, including
49+
the original version of the Work and any modifications or additions
50+
to that Work or Derivative Works thereof, that is intentionally
51+
submitted to Licensor for inclusion in the Work by the copyright owner
52+
or by an individual or Legal Entity authorized to submit on behalf of
53+
the copyright owner. For the purposes of this definition, "submitted"
54+
means any form of electronic, verbal, or written communication sent
55+
to the Licensor or its representatives, including but not limited to
56+
communication on electronic mailing lists, source code control systems,
57+
and issue tracking systems that are managed by, or on behalf of, the
58+
Licensor for the purpose of discussing and improving the Work, but
59+
excluding communication that is conspicuously marked or otherwise
60+
designated in writing by the copyright owner as "Not a Contribution."
61+
62+
"Contributor" shall mean Licensor and any individual or Legal Entity
63+
on behalf of whom a Contribution has been received by Licensor and
64+
subsequently incorporated within the Work.
65+
66+
2. Grant of Copyright License. Subject to the terms and conditions of
67+
this License, each Contributor hereby grants to You a perpetual,
68+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69+
copyright license to reproduce, prepare Derivative Works of,
70+
publicly display, publicly perform, sublicense, and distribute the
71+
Work and such Derivative Works in Source or Object form.
72+
73+
3. Grant of Patent License. Subject to the terms and conditions of
74+
this License, each Contributor hereby grants to You a perpetual,
75+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76+
(except as stated in this section) patent license to make, have made,
77+
use, offer to sell, sell, import, and otherwise transfer the Work,
78+
where such license applies only to those patent claims licensable
79+
by such Contributor that are necessarily infringed by their
80+
Contribution(s) alone or by combination of their Contribution(s)
81+
with the Work to which such Contribution(s) was submitted. If You
82+
institute patent litigation against any entity (including a
83+
cross-claim or counterclaim in a lawsuit) alleging that the Work
84+
or a Contribution incorporated within the Work constitutes direct
85+
or contributory patent infringement, then any patent licenses
86+
granted to You under this License for that Work shall terminate
87+
as of the date such litigation is filed.
88+
89+
4. Redistribution. You may reproduce and distribute copies of the
90+
Work or Derivative Works thereof in any medium, with or without
91+
modifications, and in Source or Object form, provided that You
92+
meet the following conditions:
93+
94+
(a) You must give any other recipients of the Work or
95+
Derivative Works a copy of this License; and
96+
97+
(b) You must cause any modified files to carry prominent notices
98+
stating that You changed the files; and
99+
100+
(c) You must retain, in the Source form of any Derivative Works
101+
that You distribute, all copyright, patent, trademark, and
102+
attribution notices from the Source form of the Work,
103+
excluding those notices that do not pertain to any part of
104+
the Derivative Works; and
105+
106+
(d) If the Work includes a "NOTICE" text file as part of its
107+
distribution, then any Derivative Works that You distribute must
108+
include a readable copy of the attribution notices contained
109+
within such NOTICE file, excluding those notices that do not
110+
pertain to any part of the Derivative Works, in at least one
111+
of the following places: within a NOTICE text file distributed
112+
as part of the Derivative Works; within the Source form or
113+
documentation, if provided along with the Derivative Works; or,
114+
within a display generated by the Derivative Works, if and
115+
wherever such third-party notices normally appear. The contents
116+
of the NOTICE file are for informational purposes only and
117+
do not modify the License. You may add Your own attribution
118+
notices within Derivative Works that You distribute, alongside
119+
or as an addendum to the NOTICE text from the Work, provided
120+
that such additional attribution notices cannot be construed
121+
as modifying the License.
122+
123+
You may add Your own copyright statement to Your modifications and
124+
may provide additional or different license terms and conditions
125+
for use, reproduction, or distribution of Your modifications, or
126+
for any such Derivative Works as a whole, provided Your use,
127+
reproduction, and distribution of the Work otherwise complies with
128+
the conditions stated in this License.
129+
130+
5. Submission of Contributions. Unless You explicitly state otherwise,
131+
any Contribution intentionally submitted for inclusion in the Work
132+
by You to the Licensor shall be under the terms and conditions of
133+
this License, without any additional terms or conditions.
134+
Notwithstanding the above, nothing herein shall supersede or modify
135+
the terms of any separate license agreement you may have executed
136+
with Licensor regarding such Contributions.
137+
138+
6. Trademarks. This License does not grant permission to use the trade
139+
names, trademarks, service marks, or product names of the Licensor,
140+
except as required for reasonable and customary use in describing the
141+
origin of the Work and reproducing the content of the NOTICE file.
142+
143+
7. Disclaimer of Warranty. Unless required by applicable law or
144+
agreed to in writing, Licensor provides the Work (and each
145+
Contributor provides its Contributions) on an "AS IS" BASIS,
146+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147+
implied, including, without limitation, any warranties or conditions
148+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149+
PARTICULAR PURPOSE. You are solely responsible for determining the
150+
appropriateness of using or redistributing the Work and assume any
151+
risks associated with Your exercise of permissions under this License.
152+
153+
8. Limitation of Liability. In no event and under no legal theory,
154+
whether in tort (including negligence), contract, or otherwise,
155+
unless required by applicable law (such as deliberate and grossly
156+
negligent acts) or agreed to in writing, shall any Contributor be
157+
liable to You for damages, including any direct, indirect, special,
158+
incidental, or consequential damages of any character arising as a
159+
result of this License or out of the use or inability to use the
160+
Work (including but not limited to damages for loss of goodwill,
161+
work stoppage, computer failure or malfunction, or any and all
162+
other commercial damages or losses), even if such Contributor
163+
has been advised of the possibility of such damages.
164+
165+
9. Accepting Warranty or Additional Liability. While redistributing
166+
the Work or Derivative Works thereof, You may choose to offer,
167+
and charge a fee for, acceptance of support, warranty, indemnity,
168+
or other liability obligations and/or rights consistent with this
169+
License. However, in accepting such obligations, You may act only
170+
on Your own behalf and on Your sole responsibility, not on behalf
171+
of any other Contributor, and only if You agree to indemnify,
172+
defend, and hold each Contributor harmless for any liability
173+
incurred by, or claims asserted against, such Contributor by reason
174+
of your accepting any such warranty or additional liability.
175+
176+
END OF TERMS AND CONDITIONS
177+
178+
APPENDIX: How to apply the Apache License to your work.
179+
180+
To apply the Apache License to your work, attach the following
181+
boilerplate notice, with the fields enclosed by brackets "{}"
182+
replaced with your own identifying information. (Don't include
183+
the brackets!) The text should be enclosed in the appropriate
184+
comment syntax for the file format. We also recommend that a
185+
file or class name and description of purpose be included on the
186+
same "printed page" as the copyright notice for easier
187+
identification within third-party archives.
188+
189+
Copyright {yyyy} {name of copyright owner}
190+
191+
Licensed under the Apache License, Version 2.0 (the "License");
192+
you may not use this file except in compliance with the License.
193+
You may obtain a copy of the License at
194+
195+
http://www.apache.org/licenses/LICENSE-2.0
196+
197+
Unless required by applicable law or agreed to in writing, software
198+
distributed under the License is distributed on an "AS IS" BASIS,
199+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200+
See the License for the specific language governing permissions and
201+
limitations under the License.

‎README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Quantum Circuit Simulator
2+
3+
A browser based quantum circuit editor and simulator.
4+
5+
Application here: [http://davy.wtf/quantum](http://davy.wtf/quantum)

‎css/base.css

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
body {
2+
margin: 0;
3+
padding: 0;
4+
}
5+
#content {
6+
margin-left: 10px;
7+
}
8+
canvas:focus {
9+
outline: 0;
10+
}
11+
#toolbar {
12+
border: solid 1px #eee;
13+
margin-bottom: 10px;
14+
}
15+
#toolbar div.gate {
16+
position: relative;
17+
margin: 5px;
18+
display: inline-block;
19+
}
20+
#toolbar div.gate > div {
21+
position: absolute;
22+
left: 0;
23+
top: 0;
24+
display: inline-block;
25+
width: 40px;
26+
height: 40px;
27+
opacity: 0;
28+
}
29+
#toolbar div.gate:hover > div {
30+
background-color: #79f;
31+
opacity: 0.25;
32+
}
33+
#toolbar div.gate.active > div {
34+
background-color: #f90;
35+
opacity: 0.5;
36+
}
37+
#amplitudes-container {
38+
float:left;
39+
margin-left: 50px;
40+
display: none;
41+
}
42+
#amplitudes-scrollbox {
43+
max-height: 480px;
44+
overflow-y: scroll;
45+
}
46+
#hide-impossible {
47+
color: #ccc;
48+
text-align: center;
49+
margin-bottom: 20px;
50+
cursor: pointer;
51+
}
52+
#controls {
53+
margin-top: 10px;
54+
}
55+
#progress {
56+
width: 100%;
57+
height: 10px;
58+
background-color: #79f;
59+
display: none;
60+
}
61+
#progress > div {
62+
width: 0px;
63+
height: 10px;
64+
background-color: #f90;
65+
border-right: solid 2px #fff;
66+
display: inline-block;
67+
}
68+
#footer {
69+
position: absolute;
70+
right: 10px;
71+
bottom: 0;
72+
color: #666;
73+
}
74+
75+
#menubar {
76+
width: 100%;
77+
margin-bottom: 10px;
78+
background: #aaa;
79+
border-bottom: solid 1px #999;
80+
color: #fff;
81+
font-family: Monospace;
82+
font-size: 16px;
83+
}
84+
#menubar > li {
85+
margin-right: 2px;
86+
}
87+
ul.drop a {
88+
display:block;
89+
text-decoration: none;
90+
color: #fff;
91+
font-family: Monospace;
92+
}
93+
ul.drop, ul.drop li, ul.drop ul {
94+
list-style: none;
95+
margin: 0;
96+
padding: 0;
97+
}
98+
ul.drop {
99+
position: relative;
100+
z-index: 597;
101+
float: left;
102+
}
103+
ul.drop li {
104+
float: left;
105+
line-height: 1.3em;
106+
vertical-align: middle;
107+
zoom: 1;
108+
padding: 2px 10px;
109+
background: #aaa;
110+
}
111+
ul.drop li.hover, ul.drop li:hover {
112+
position: relative;
113+
z-index: 599;
114+
cursor: default;
115+
background: #777;
116+
}
117+
ul.drop ul {
118+
visibility: hidden;
119+
position: absolute;
120+
top: 100%;
121+
left: 0;
122+
z-index: 598;
123+
width: 200px;
124+
border: solid 1px #999;
125+
}
126+
ul.drop ul li {
127+
float: none;
128+
}
129+
ul.drop ul ul {
130+
top: -1px;
131+
left: 100%;
132+
}
133+
ul.drop li:hover > ul {
134+
visibility: visible
135+
}
136+
137+
#modal {
138+
position: absolute;
139+
top: 0;
140+
left: 0;
141+
width: 100%;
142+
height: 100%;
143+
background: rgba(0, 0, 0, 0.5);
144+
z-index: 900;
145+
text-align: center;
146+
display: none;
147+
}
148+
149+
#modal div {
150+
margin-top: 50px;
151+
display: inline-block;
152+
width: 640px;
153+
height: 480px;
154+
overflow-y: scroll;
155+
background-color: #fff;
156+
z-index: 901;
157+
padding: 20px;
158+
}

‎images/favicon.ico

318 Bytes
Binary file not shown.

‎images/r.png

2.12 KB
Loading

‎index.html

+158-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,158 @@
1-
"My Page"
1+
<html>
2+
<head>
3+
<link rel="shortcut icon" href="images/favicon.ico">
4+
<link rel="stylesheet" href="css/base.css">
5+
<title>Quantum Circuit Simulator</title>
6+
<script src="js/libs/numeric.js"></script>
7+
<script src="js/libs/processing.js"></script>
8+
<script src="js/bundle.js"></script>
9+
</head>
10+
<body>
11+
<div>
12+
<ul id="menubar" class="drop">
13+
<li>
14+
Workspace
15+
<ul>
16+
<li><a href="#" onclick="window.open(window.location)">New</a></li>
17+
<li>
18+
Examples
19+
<ul id="examples">
20+
</ul>
21+
</li>
22+
<li><a id="importJSON" href="#">Import JSON</a></li>
23+
<li><a id="exportJSON" href="#">Export JSON</a></li>
24+
</ul>
25+
</li>
26+
<li>
27+
Circuit
28+
<ul>
29+
<li>
30+
<a id="evaluate" href="#" title="Evaluate circuit">
31+
Evaluate
32+
<div style="float:right; font-size: 11px">
33+
Enter
34+
</div>
35+
</a>
36+
</li>
37+
<li>
38+
<a id="compile" href="#" title="Compile circuit to gate">
39+
Compile
40+
<div style="float:right; font-size: 11px">
41+
Ctrl+S
42+
</div>
43+
</a>
44+
</li>
45+
<li><a id="exportImage" href="#">Export Image</a></li>
46+
<li><a id="exportMatrix" href="#">Export Matrix</a></li>
47+
<li id="nqubits">
48+
<span>Qubits</span>
49+
<ul></ul>
50+
</li>
51+
<li><a id="reset" href="#">Reset</a></li>
52+
</ul>
53+
</li>
54+
<li style="float:right"><a id="about" href="#">About</a></li>
55+
</ul>
56+
</div>
57+
<div style="clear:both">
58+
<div id="content" style="float:left">
59+
<div id="toolbar">
60+
<div class="std"></div>
61+
<div class="user"></div>
62+
</div>
63+
<div>
64+
<canvas id="canvas"></canvas>
65+
</div>
66+
<div id="progress"><div></div></div>
67+
</div>
68+
<div id="amplitudes-container">
69+
<div id="hide-impossible">(show all)</div>
70+
<div id="amplitudes-scrollbox">
71+
<table id="amplitudes"></table>
72+
</div>
73+
</div>
74+
</div>
75+
<div id="modal">
76+
<div style="text-align: left">
77+
<h1 style="text-align: center">Quantum Circuit Simulator</h1>
78+
<p>
79+
Written by <a href="http://www.davyw.com">Davy Wybiral</a>.
80+
<br>
81+
Contributions by <a href="http://molehair.noip.me">Jiman Hwang</a>
82+
</p>
83+
<p>
84+
<h3>Purpose:</h3>
85+
This is a quantum circuit simulator designed to function as a learning tool for anyone interested in quantum computing.
86+
It has a friendly GUI for constructing and evaluating quantum circuits.
87+
Rather than constructing one simple circuit, it's designed to support modular circuit design.
88+
Any circuit you make can be compiled into a gate for use in other circuits.
89+
<br><br>
90+
The default gates in use were chosen because they appear frequently in literature.
91+
There does seem to be a mix of conventions regarding rotation gates. To avoid confusion,
92+
this is the matrix used to construct all of the Rx gates used by this application:
93+
<p style="text-align: center"><img src="images/r.png"></p>
94+
</p>
95+
<p>
96+
<h3>The basics:</h3>
97+
<ul>
98+
<li>Click on the qubits to the left of the circuit wires to toggle the input state.</li>
99+
<li>Click on a quantum gate (above the circuit wires) to select that gate type.
100+
Then click on a circuit wire to place the selected gate there.</li>
101+
<li>For gates over multiple qubits, such as the swap or QFT gates, click and drag across desired qubits.</li>
102+
<li>Right clicking will delete a gate.</li>
103+
</ul>
104+
</p>
105+
<p>
106+
<h3>Controls:</h3>
107+
<ul>
108+
<li>Any gate can be made into a controlled version of itself by selecting the control gate (the black dot)
109+
and dragging from the control qubit to the target gate.</li>
110+
<li>Dragging a control onto an X gate will result in a CNot gate.</li>
111+
<li>Multiple controls can be added to a single gate.</li>
112+
<li>Dragging two controls to an X will result in a Toffoli gate.</li>
113+
<li>Right clicking on a control will remove it from the gate without removing the gate itself.</li>
114+
</ul>
115+
</p>
116+
<p>
117+
<h3>Evaluate:</h3>
118+
<ul>
119+
<li>You can evaluate your circuit by either clicking the <b>Evaluate</b> option in the <b>Circuit</b> menu or by pressing <b>Enter</b>.</li>
120+
<li>Evaluating the circuit will apply the circuit to the current input state (on the left of the circuit wires)
121+
and display a table of resulting probabilities (on the right of the circuit wires).</li>
122+
<li>Each line in the probabilities table is of the form "a+bi|x> p%" where "a+bi" is a complex number (the amplitude),
123+
"x" is a possible binary state for the entire system, and "p" is a percent probability that a measurement would result in that state.</li>
124+
<li>By default, states with 0% probability are hidden. Click "(show all)" above the table to display zero and nonzero probabilities.</li>
125+
</ul>
126+
</p>
127+
<p>
128+
<h3>Compile:</h3>
129+
<ul>
130+
<li>You can compile your circuit by either clicking the <b>Compile</b> option in the <b>Circuit</b> menu or by pressing <b>Ctrl+S</b>.</li>
131+
<li>Compiling your circuit will create a gate containing the visible circuit to be used in larger circuits.</li>
132+
<li>Once compiled, you can double-click on the gate in the toolbar to open it's circuit.</li>
133+
<li>Saving a gate with the same name as an existing one will overwrite the existing gate.
134+
This does not update gates that use this circuit. They will need to be "recompiled" too.</li>
135+
</ul>
136+
</p>
137+
<p>
138+
<h3>Exporting:</h3>
139+
<ul>
140+
<li>You can export all of the gates you've created into a JSON format by clicking on the <b>Export JSON</b>
141+
option in the <b>Workspace</b> menu.</li>
142+
<li>This exported JSON text can be reimported at a later time by clicking on the <b>Import JSON</b>
143+
option in the <b>Workspace</b> menu and then pasting the JSON text into the prompt.</li>
144+
<li>You can export the circuit diagram as an image by clicking the <b>Export Image</b> option in the <b>Circuit</b> menu.</li>
145+
<li>You can export the circuit as a CSV matrix of complex values by clicking the <b>Export Matrix</b> option in the <b>Circuit</b> menu.</li>
146+
</ul>
147+
</p>
148+
<p>
149+
<h3>Resizing:</h3>
150+
<ul>
151+
<li>You can resize your circuit by changing the <b>Qubits</b> setting in the <b>Circuit</b> menu.</li>
152+
<li>If the new size is smaller than the existing circuit, gates that don't fit will be removed.</li>
153+
</ul>
154+
</p>
155+
</div>
156+
</div>
157+
</body>
158+
</html>

‎js/application.js

+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
const Circuit = require('./circuit');
2+
const Draw = require('./draw');
3+
const Editor = require('./editor');
4+
const Gate = require('./gate');
5+
const Workspace = require('./workspace');
6+
7+
module.exports = class Application {
8+
9+
constructor(canvas, nqubits) {
10+
const app = this;
11+
this.workspace = new Workspace(app);
12+
const circuit = this.circuit = new Circuit(app, nqubits);
13+
const editor = this.editor = new Editor(app, canvas);
14+
const toolbar = document.querySelector('#toolbar');
15+
toolbar.onclick = evt => {
16+
let target = findParent(evt.target, el => {
17+
return el.className && el.className.indexOf('gate') > -1;
18+
});
19+
if (target) {
20+
const current = document.querySelector('#toolbar div.gate.active');
21+
if (current) {
22+
current.className = 'gate';
23+
}
24+
target.className = 'active gate';
25+
editor.activeGate = app.workspace.gates[target.dataset.type];
26+
}
27+
};
28+
const userTools = document.querySelector('#toolbar .user');
29+
userTools.ondblclick = evt => {
30+
// Open gate from toolbar
31+
evt.preventDefault();
32+
let target = findParent(evt.target, el => {
33+
return el.className && el.className.indexOf('gate') > -1;
34+
});
35+
if (target) {
36+
let ok = true;
37+
if (app.circuit.gates.length > 0) {
38+
// Only confirm if circuit isn't empty
39+
ok = confirm('Load gate: ' + target.dataset.type + '?');
40+
}
41+
if (ok) {
42+
app.editCircuit(app.workspace.gates[target.dataset.type]);
43+
}
44+
}
45+
};
46+
47+
document.querySelectorAll('#toolbar div.gate')[0].click();
48+
}
49+
50+
/*
51+
Set current "circuit" to that of some "gate" and update the interface
52+
for the new circuit.
53+
*/
54+
editCircuit(gate) {
55+
this.circuit = gate.circuit;
56+
this.editor.resize(gate.circuit.nqubits, this.editor.length);
57+
document.querySelector('#nqubits > span').innerHTML = 'Qubits: ' + this.circuit.nqubits;
58+
if (gate.input) {
59+
this.editor.input = gate.input;
60+
}
61+
this.editor.render();
62+
}
63+
64+
/*
65+
Add a button for a gate to the toolbar.
66+
"type" should be either "std" or "user" (where "std" is a standard gate and
67+
"user" is a user-created gate)
68+
"name" is the name of the gate used by the workspace
69+
"title" is a human-readable name for the gate
70+
*/
71+
addToolbarButton(type, name, title) {
72+
const canvas = document.createElement('canvas');
73+
const draw = new Draw(canvas, 1, 1);
74+
const tool = document.createElement('div');
75+
tool.dataset.type = name;
76+
tool.className = "gate";
77+
if (title) {
78+
tool.title = title;
79+
}
80+
draw.clear();
81+
if (name == 'swap') {
82+
draw.swap(20, 20);
83+
} else if (name == 'control') {
84+
draw.control(20, 20);
85+
} else if (name == 'cnot') {
86+
draw.not(20, 20);
87+
} else {
88+
draw.gate(20, 20, 1, name.toUpperCase());
89+
}
90+
const img = document.createElement('img');
91+
img.src = canvas.toDataURL();
92+
tool.appendChild(img);
93+
tool.appendChild(document.createElement('div'));
94+
document.querySelector('#toolbar .' + type).appendChild(tool);
95+
}
96+
97+
/*
98+
Load a new workspace in from a json object, overwriting the current one.
99+
JSON struct looks like:
100+
{
101+
"circuit": [
102+
{"type": "h", "time": 0, "targets": [0], "controls": []},
103+
{"type": "x", "time": 1, "targets": [1], "controls": [0]}
104+
],
105+
"qubits": 2,
106+
"input": [0, 0]
107+
}
108+
And can also contain a "gates" property which is a list of the user defined
109+
gates in the circuit.
110+
*/
111+
loadWorkspace(json) {
112+
document.querySelector('#toolbar .std').innerHTML = '';
113+
document.querySelector('#toolbar .user').innerHTML = '';
114+
this.workspace = new Workspace(this);
115+
if (json.gates) {
116+
for (let i = 0 ; i < json.gates.length; i++) {
117+
const gate = json.gates[i];
118+
const circuit = new Circuit(this, gate.qubits);
119+
for (let j = 0; j < gate.circuit.length; j++) {
120+
circuit.addGate(new Gate(
121+
this.workspace.gates[gate.circuit[j].type],
122+
gate.circuit[j].time + 1,
123+
gate.circuit[j].targets,
124+
gate.circuit[j].controls
125+
));
126+
}
127+
this.workspace.addGate({
128+
name: gate.name,
129+
qubits: gate.qubits,
130+
matrix: gate.matrix,
131+
fn: gate.fn,
132+
title: gate.title,
133+
circuit: circuit
134+
});
135+
}
136+
}
137+
this.circuit = new Circuit(this, json.qubits);
138+
for (let j = 0; j < json.circuit.length; j++) {
139+
this.circuit.addGate(new Gate(
140+
this.workspace.gates[json.circuit[j].type],
141+
json.circuit[j].time + 1,
142+
json.circuit[j].targets,
143+
json.circuit[j].controls
144+
));
145+
}
146+
this.editor.resize(this.circuit.nqubits, this.editor.length);
147+
this.editor.input = json.input;
148+
document.querySelector('#nqubits > span').innerHTML = 'Qubits: ' + this.circuit.nqubits;
149+
this.compileAll();
150+
this.editor.render();
151+
}
152+
153+
/*
154+
Asynchronously compile every user defined gate in the workspace.
155+
XXX: This should probably be a method of Workspace
156+
*/
157+
compileAll() {
158+
const app = this;
159+
const todo = [];
160+
const workspace = this.workspace;
161+
document.querySelectorAll('#toolbar .user div.gate').forEach(el => {
162+
const name = el.dataset.type;
163+
const type = workspace.gates[name];
164+
if (!type.matrix) {
165+
todo.push(type);
166+
}
167+
});
168+
(function loop(i) {
169+
if (i < todo.length) {
170+
const n = Math.pow(2, todo[i].circuit.nqubits);
171+
const I = new numeric.T(
172+
numeric.identity(n),
173+
numeric.rep([n, n], 0)
174+
);
175+
app.applyCircuit(todo[i].circuit, I, U => {
176+
todo[i].matrix = U;
177+
setTimeout(function() {
178+
loop(i + 1);
179+
}, 5);
180+
});
181+
}
182+
})(0);
183+
}
184+
185+
/*
186+
Applies circuit to matrix and passes result to callback
187+
*/
188+
applyCircuit(circuit, x, callback) {
189+
const wrapper = document.querySelector('#progress');
190+
wrapper.style.display = 'inline-block';
191+
const progress = document.querySelector('#progress > div');
192+
progress.width = 0;
193+
circuit.evaluate(x, percent => {
194+
progress.style.width = wrapper.clientWidth * percent;
195+
}, x => {
196+
wrapper.style.display = 'none';
197+
callback(x);
198+
});
199+
}
200+
201+
}
202+
203+
204+
/*
205+
Search ancestors in DOM.
206+
*/
207+
const findParent = (el, test) => {
208+
while (el.parentNode && !test(el)) {
209+
el = el.parentNode;
210+
}
211+
if (el !== document) {
212+
return el;
213+
}
214+
};

‎js/bundle.js

+1,252
Large diffs are not rendered by default.

‎js/circuit.js

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
const Gate = require('./gate');
2+
const quantum = require('./quantum');
3+
4+
module.exports = class Circuit {
5+
6+
constructor(app, nqubits) {
7+
this.app = app;
8+
this.nqubits = nqubits;
9+
this.gates = [];
10+
this.matrix = null;
11+
this.inputs = [];
12+
}
13+
14+
/*
15+
Return JSON-capable object representation of this circuit
16+
*/
17+
toJSON() {
18+
const circuit = [];
19+
for (let i = 0; i < this.gates.length; i++) {
20+
circuit.push({
21+
type: this.gates[i].type.name,
22+
time: this.gates[i].time - 1,
23+
targets: this.gates[i].targets,
24+
controls: this.gates[i].controls
25+
});
26+
}
27+
return circuit;
28+
}
29+
30+
/*
31+
Return a copy of this circuit
32+
*/
33+
copy() {
34+
const circuit = new Circuit(this.app, this.nqubits);
35+
for (let i = 0; i < this.gates.length; i++) {
36+
const gate = this.gates[i];
37+
circuit.addGate(new Gate(
38+
gate.type,
39+
gate.time,
40+
gate.targets,
41+
gate.controls
42+
));
43+
}
44+
return circuit;
45+
}
46+
47+
/*
48+
Add a gate to this circuit
49+
(note that this destroys the current compiled matrix)
50+
*/
51+
addGate(gate) {
52+
this.gates.push(gate);
53+
this.matrix = null;
54+
}
55+
56+
/*
57+
Remove a gate from this circuit
58+
(note that this destroys the current compiled matrix)
59+
*/
60+
removeGate(gate) {
61+
this.gates.splice(this.gates.indexOf(gate), 1);
62+
this.matrix = null;
63+
}
64+
65+
/*
66+
Evaluate this circuit on "x" (a matrix or state vector) calling "progress"
67+
along the way with the percentage of completion and finally calling
68+
"callback" with the result.
69+
*/
70+
evaluate(x, progress, callback) {
71+
const circuit = this;
72+
(function applyLoop(i) {
73+
progress(i / circuit.gates.length);
74+
if (i < circuit.gates.length) {
75+
let U;
76+
const gate = circuit.gates[i];
77+
if (gate.type.qubits < Infinity) {
78+
U = gate.type.matrix;
79+
} else {
80+
U = gate.type.fn(gate.targets.length);
81+
}
82+
for (var j = 0; j < gate.controls.length; j++) {
83+
U = quantum.controlled(U);
84+
}
85+
var qubits = gate.controls.concat(gate.targets);
86+
//x = x.dot(quantum.expandMatrix(circuit.nqubits, U, qubits));
87+
x = quantum.expandMatrix(circuit.nqubits, U, qubits).dot(x);
88+
setTimeout(() => applyLoop(i + 1), 1);
89+
} else {
90+
callback(x);
91+
}
92+
})(0);
93+
}
94+
95+
}
96+

‎js/draw.js

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
Rendering primatives for circuit grid
3+
*/
4+
module.exports = class Draw {
5+
6+
constructor(canvas, nqubits, length) {
7+
this.canvas = canvas;
8+
this.gfx = new Processing(canvas);
9+
this.nqubits = nqubits;
10+
this.length = length;
11+
this.gfx.textFont(this.gfx.loadFont('monospace'));
12+
this.gfx.textAlign(this.gfx.CENTER);
13+
this.gfx.size(length * 40, nqubits * 40);
14+
}
15+
16+
resize(nqubits, length) {
17+
this.nqubits = nqubits;
18+
this.length = length;
19+
this.gfx.size(length * 40, nqubits * 40);
20+
}
21+
22+
clear() {
23+
this.gfx.background(255);
24+
for (let i = 0; i < this.nqubits; i++) {
25+
this.gfx.line(0, i * 40 + 20, this.length * 40, i * 40 + 20);
26+
}
27+
}
28+
29+
selection(x, qubits, r, g, b, a) {
30+
this.gfx.noStroke();
31+
this.gfx.fill(r, g, b, a);
32+
for (let i = 0; i < qubits.length; i++) {
33+
this.gfx.rect(x, qubits[i] * 40, 40, 40);
34+
}
35+
this.gfx.fill(255);
36+
this.gfx.stroke(0);
37+
}
38+
39+
qubit(x, y, h, state) {
40+
this.gfx.textSize(17);
41+
this.gfx.noStroke();
42+
this.gfx.fill(255);
43+
this.gfx.rect(x - 20, y - 17, 40, h * 40 - 6);
44+
this.gfx.fill(0);
45+
this.gfx.text(state ? '|1>' : '|0>', x, (y + (h / 2) * 40) - 15);
46+
this.gfx.fill(255);
47+
this.gfx.stroke(0);
48+
this.gfx.textSize(11);
49+
}
50+
51+
gate(x, y, h, text) {
52+
this.gfx.fill(255);
53+
this.gfx.rect(x - 17, y - 17, 40 - 6, h * 40 - 6);
54+
this.gfx.fill(0);
55+
this.gfx.text(text, x, (y + (h / 2) * 40) - 17);
56+
this.gfx.fill(255);
57+
}
58+
59+
swap(x, y) {
60+
this.gfx.line(x - 5, y - 5, x + 5, y + 5);
61+
this.gfx.line(x - 5, y + 5, x + 5, y - 5);
62+
}
63+
64+
not(x, y) {
65+
this.gfx.noFill();
66+
this.gfx.ellipse(x, y, 20, 20);
67+
this.gfx.fill(255);
68+
this.gfx.line(x - 9, y, x + 9, y);
69+
this.gfx.line(x, y - 9, x, y + 9);
70+
}
71+
72+
wire(x, y1, y2) {
73+
this.gfx.line(x, y1, x, y2);
74+
}
75+
76+
control(x, y) {
77+
this.gfx.fill(0);
78+
this.gfx.ellipse(x, y, 10, 10);
79+
}
80+
81+
}

‎js/editor.js

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
const Draw = require('./draw');
2+
const Gate = require('./gate');
3+
4+
module.exports = class Editor {
5+
6+
constructor(app, canvas) {
7+
const length = 20;
8+
this.app = app;
9+
this.draw = new Draw(canvas, app.circuit.nqubits, length);
10+
this.bindEvents();
11+
this.length = length;
12+
this.input = numeric.rep([app.circuit.nqubits], 0);
13+
this.render();
14+
}
15+
16+
/*
17+
Resize editor and redraw
18+
*/
19+
resize(nqubits, length) {
20+
this.draw.resize(nqubits, length);
21+
this.app.circuit.nqubits = nqubits;
22+
this.length = length;
23+
this.input = numeric.rep([nqubits], 0);
24+
this.render();
25+
}
26+
27+
leftClick(time, qubit) {
28+
const editor = this;
29+
const node = this.draw.canvas;
30+
const circuit = this.app.circuit;
31+
// Clear mouse events
32+
node.onmouseup = null;
33+
node.onmouseout = null;
34+
node.onmousedown = null;
35+
node.onmousemove = null;
36+
node.onmousemove = evt2 => {
37+
// Handles highlighting while dragging
38+
if (typeof evt2.offsetY == 'undefined') {
39+
evt2.offsetY = evt2.layerY - node.offsetTop;
40+
}
41+
const qubits = editor.getSelection(qubit, Math.floor(evt2.offsetY / 40));
42+
editor.render();
43+
editor.draw.selection(time * 40, qubits, 255, 153, 0, 128);
44+
};
45+
node.onmouseup = evt2 => {
46+
if (typeof evt2.offsetY == 'undefined') {
47+
evt2.offsetY = evt2.pageY - node.offsetTop;
48+
}
49+
// Get array of selected qubits
50+
const qubits = editor.getSelection(qubit, Math.floor(evt2.offsetY / 40));
51+
const type = editor.activeGate;
52+
if (time == 0) {
53+
// Toggle inputs
54+
for (let i = 0; i < qubits.length; i++) {
55+
editor.input[qubits[i]] = 1 - editor.input[qubits[i]];
56+
}
57+
} else if (type.name == 'control') {
58+
// Add control to a gate (if possible)
59+
let collisionA = false;
60+
let collisionB = false;
61+
for (let j = 0; j < circuit.gates.length; j++) {
62+
if (circuit.gates[j].touching(time, qubits[0])) {
63+
collisionA = circuit.gates[j];
64+
}
65+
if (circuit.gates[j].touching(time, qubits[qubits.length - 1])) {
66+
collisionB = circuit.gates[j];
67+
}
68+
}
69+
if ((collisionA === collisionB || !collisionA) && collisionB) {
70+
collisionB.addControl(qubits[0]);
71+
circuit.matrix = null;
72+
}
73+
} else {
74+
// Otherwise we're creating a new gate
75+
if (type.qubits == 1 && qubits.length > 1) {
76+
for (let i = 0; i < qubits.length; i++) {
77+
editor.createGate(type, time, [qubits[i]]);
78+
}
79+
} else if (type.qubits == qubits.length || type.qubits == Infinity || type.name == 'cnot' || type.name == 'swap') {
80+
editor.createGate(type, time, qubits);
81+
}
82+
}
83+
// Clear mouse events
84+
node.onmouseup = null;
85+
node.onmouseout = null;
86+
node.onmousedown = null;
87+
node.onmousemove = null;
88+
this.bindEvents();
89+
editor.render();
90+
};
91+
};
92+
93+
rightClick(time, qubit) {
94+
const circuit = this.app.circuit;
95+
const editor = this;
96+
const old = circuit.gates.length;
97+
let collision = false;
98+
if (time == 0) {
99+
// Set input to 0
100+
this.input[qubit] = 0;
101+
} else {
102+
// Find gate or control and remove it
103+
for (let j = 0; j < circuit.gates.length; j++) {
104+
if (circuit.gates[j].touching(time, qubit)) {
105+
collision = circuit.gates[j];
106+
}
107+
}
108+
if (collision) {
109+
let control = collision.controls.indexOf(qubit);
110+
if (control < 0) {
111+
circuit.removeGate(collision);
112+
} else {
113+
collision.removeControl(qubit);
114+
circuit.matrix = null;
115+
}
116+
editor.render();
117+
}
118+
}
119+
}
120+
121+
bindEvents() {
122+
const editor = this;
123+
const node = this.draw.canvas;
124+
node.onmouseout = evt => {
125+
// This stops the mouseover highlight from lingering after mouseout
126+
editor.render();
127+
};
128+
node.onmousemove = evt => {
129+
// Highlight tile under mouse
130+
if (typeof evt.offsetX == 'undefined') {
131+
evt.offsetX = evt.pageX - node.offsetLeft;
132+
}
133+
if (typeof evt.offsetY == 'undefined') {
134+
evt.offsetY = evt.pageY - node.offsetTop;
135+
}
136+
editor.render();
137+
const x = Math.floor(evt.offsetX / 40);
138+
const y = Math.floor(evt.offsetY / 40);
139+
editor.draw.selection(x * 40, [y], 119, 153, 255, 64);
140+
};
141+
142+
node.onmousedown = evt => {
143+
// Dispatch left/right click events
144+
if (typeof evt.offsetX == 'undefined') {
145+
evt.offsetX = evt.pageX - node.offsetLeft;
146+
}
147+
if (typeof evt.offsetY == 'undefined') {
148+
evt.offsetY = evt.pageY - node.offsetTop;
149+
}
150+
const x = Math.floor(evt.offsetX / 40);
151+
const y = Math.floor(evt.offsetY / 40);
152+
if (evt.which == 1) {
153+
editor.leftClick(x, y);
154+
} else if (evt.which == 2 || evt.which == 3) {
155+
editor.rightClick(x, y);
156+
}
157+
};
158+
}
159+
160+
createGate(type, time, qubits) {
161+
const circuit = this.app.circuit;
162+
let collision = false;
163+
// Find collision (can't add a gate where one already exists)
164+
for (let i = 0; i < qubits.length; i++) {
165+
for (let j = 0; j < circuit.gates.length; j++) {
166+
if (circuit.gates[j].touching(time, qubits[i])) {
167+
collision = circuit.gates[j];
168+
}
169+
}
170+
}
171+
if (!collision) {
172+
if (type.name == 'cnot' || type.name == 'swap') {
173+
// Create cnot or swap (gates that can span multiple qubits but only
174+
// actually use two.
175+
if (qubits.length < 2) {
176+
return console.warn(type + ' gate requires two qubits');
177+
} else {
178+
qubits = [qubits[0], qubits[qubits.length - 1]];
179+
}
180+
} else {
181+
qubits.sort();
182+
}
183+
if (type.name == 'cnot') {
184+
// cnot is really a controlled x
185+
circuit.addGate(new Gate(this.app.workspace.gates.x, time, [qubits[1]], [qubits[0]]));
186+
} else {
187+
circuit.addGate(new Gate(type, time, qubits));
188+
}
189+
}
190+
}
191+
192+
/*
193+
Return array of all qubit indices between y1 and y2
194+
*/
195+
getSelection(y1, y2) {
196+
const dy = y2 - y1;
197+
const h = Math.abs(dy) + 1;
198+
const qubits = [];
199+
if (dy < 0) {
200+
for (let i = y1; i > y1 - h; i--) {
201+
qubits.push(i)
202+
}
203+
} else {
204+
for (let i = y1; i < y1 + h; i++) {
205+
qubits.push(i)
206+
}
207+
}
208+
return qubits;
209+
}
210+
211+
/*
212+
Render entire editor
213+
*/
214+
render() {
215+
this.draw.clear();
216+
for (let i = 0; i < this.app.circuit.gates.length; i++) {
217+
this.app.circuit.gates[i].render(this.draw);
218+
}
219+
for (let i = 0; i < this.app.circuit.nqubits; i++) {
220+
this.draw.qubit(20, 20 + i * 40, 1, this.input[i]);
221+
}
222+
}
223+
224+
}

‎js/examples.js

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
module.exports = {
2+
3+
GROVERS_ALGORITHM: {"gates":[
4+
{"name":"GROV","qubits":4,"circuit":[
5+
{"type":"h","time":0,"targets":[0],"controls":[]},
6+
{"type":"h","time":0,"targets":[2],"controls":[]},
7+
{"type":"h","time":0,"targets":[3],"controls":[]},
8+
{"type":"h","time":0,"targets":[1],"controls":[]},
9+
{"type":"x","time":1,"targets":[0],"controls":[]},
10+
{"type":"x","time":1,"targets":[1],"controls":[]},
11+
{"type":"x","time":1,"targets":[2],"controls":[]},
12+
{"type":"x","time":1,"targets":[3],"controls":[]},
13+
{"type":"z","time":2,"targets":[3],"controls":[0,1,2]},
14+
{"type":"x","time":3,"targets":[0],"controls":[]},
15+
{"type":"x","time":3,"targets":[1],"controls":[]},
16+
{"type":"x","time":3,"targets":[2],"controls":[]},
17+
{"type":"x","time":3,"targets":[3],"controls":[]},
18+
{"type":"h","time":4,"targets":[0],"controls":[]},
19+
{"type":"h","time":4,"targets":[1],"controls":[]},
20+
{"type":"h","time":4,"targets":[2],"controls":[]},
21+
{"type":"h","time":4,"targets":[3],"controls":[]}
22+
],"title":"Grover's Operator"},
23+
{"name":"F7","qubits":5,"circuit":[
24+
{"type":"x","time":0,"targets":[0],"controls":[]},
25+
{"type":"x","time":1,"targets":[4],"controls":[0,1,2,3]},
26+
{"type":"x","time":2,"targets":[0],"controls":[]}
27+
],"title":"Oracle where F(7) is flagged"},
28+
{"name":"F5","qubits":5,"circuit":[
29+
{"type":"x","time":0,"targets":[0],"controls":[]},
30+
{"type":"x","time":0,"targets":[2],"controls":[]},
31+
{"type":"x","time":1,"targets":[4],"controls":[0,1,2,3]},
32+
{"type":"x","time":2,"targets":[0],"controls":[]},
33+
{"type":"x","time":2,"targets":[2],"controls":[]},
34+
],"title":"Oracle where F(5) is flagged"},
35+
], "circuit": [
36+
{"type":"h","time":0,"targets":[2],"controls":[]},
37+
{"type":"h","time":0,"targets":[1],"controls":[]},
38+
{"type":"h","time":0,"targets":[0],"controls":[]},
39+
{"type":"h","time":0,"targets":[4],"controls":[]},
40+
{"type":"h","time":0,"targets":[3],"controls":[]},
41+
{"type":"F7","time":6,"targets":[0,1,2,3,4],"controls":[]},
42+
{"type":"GROV","time":7,"targets":[0,1,2,3],"controls":[]},
43+
{"type":"F7","time":8,"targets":[0,1,2,3,4],"controls":[]},
44+
{"type":"GROV","time":9,"targets":[0,1,2,3],"controls":[]},
45+
{"type":"F7","time":10,"targets":[0,1,2,3,4],"controls":[]},
46+
{"type":"GROV","time":11,"targets":[0,1,2,3],"controls":[]},
47+
{"type":"h","time":17,"targets":[4],"controls":[]},
48+
{"type":"x","time":18,"targets":[4],"controls":[]}
49+
],"qubits":5,"input":[0,0,0,0,1]
50+
},
51+
52+
BELL_STATE: {"circuit":[
53+
{"type":"h","time":0,"targets":[0],"controls":[]},
54+
{"type":"x","time":1,"targets":[1],"controls":[0]}
55+
],"qubits":2,"input":[0,0]},
56+
57+
QFT2: {"circuit":[
58+
{"type":"h","time":0,"targets":[0],"controls":[]},
59+
{"type":"r2","time":1,"targets":[0],"controls":[1]},
60+
{"type":"h","time":2,"targets":[1],"controls":[]},
61+
{"type":"swap","time":3,"targets":[0,1],"controls":[]}
62+
],"qubits":2,"input":[0,0]},
63+
64+
QFT4: {"circuit":[
65+
{"type":"h","time":0,"targets":[0],"controls":[]},
66+
{"type":"r2","time":1,"targets":[0],"controls":[1]},
67+
{"type":"r4","time":2,"targets":[0],"controls":[2]},
68+
{"type":"r8","time":3,"targets":[0],"controls":[3]},
69+
{"type":"h","time":4,"targets":[1],"controls":[]},
70+
{"type":"r2","time":5,"targets":[1],"controls":[2]},
71+
{"type":"r4","time":6,"targets":[1],"controls":[3]},
72+
{"type":"h","time":7,"targets":[2],"controls":[]},
73+
{"type":"r2","time":8,"targets":[2],"controls":[3]},
74+
{"type":"h","time":9,"targets":[3],"controls":[]},
75+
{"type":"swap","time":10,"targets":[2,1],"controls":[]},
76+
{"type":"swap","time":11,"targets":[0,3],"controls":[]}
77+
],"qubits":4,"input":[0,0,0,0]},
78+
79+
TOFFOLI: {"circuit":[
80+
{"type":"h","time":0,"targets":[2],"controls":[]},
81+
{"type":"s","time":1,"targets":[2],"controls":[1]},
82+
{"type":"x","time":2,"targets":[1],"controls":[0]},
83+
{"type":"s","time":3,"targets":[2],"controls":[1]},
84+
{"type":"s","time":4,"targets":[2],"controls":[1]},
85+
{"type":"s","time":5,"targets":[2],"controls":[1]},
86+
{"type":"x","time":6,"targets":[1],"controls":[0]},
87+
{"type":"s","time":7,"targets":[2],"controls":[0]},
88+
{"type":"h","time":8,"targets":[2],"controls":[]}
89+
],"qubits":3,"input":[0,0,0]},
90+
91+
TELEPORTATION: {"gates":[
92+
{"name":"TEL","qubits":3,"circuit":[
93+
{"type":"h","time":0,"targets":[0],"controls":[]},
94+
{"type":"h","time":0,"targets":[2],"controls":[]},
95+
{"type":"x","time":1,"targets":[1],"controls":[2]},
96+
{"type":"x","time":2,"targets":[1],"controls":[0]},
97+
{"type":"h","time":3,"targets":[0],"controls":[]},
98+
{"type":"x","time":3,"targets":[2],"controls":[1]},
99+
{"type":"h","time":4,"targets":[2],"controls":[]},
100+
{"type":"x","time":5,"targets":[2],"controls":[0]}
101+
],"title":"Quantum teleportation circuit"},
102+
{"name":"F","qubits":1,"circuit":[
103+
{"type":"h","time":0,"targets":[0],"controls":[]},
104+
{"type":"r4","time":1,"targets":[0],"controls":[]},
105+
{"type":"h","time":2,"targets":[0],"controls":[]},
106+
{"type":"r4","time":3,"targets":[0],"controls":[]},
107+
{"type":"h","time":4,"targets":[0],"controls":[]}
108+
],"title":"Function creating 75% |0> and 25% |1> superposition"},
109+
{"name":"MEAS","qubits":2,"circuit":[
110+
{"type":"h","time":0,"targets":[0],"controls":[]},
111+
{"type":"h","time":0,"targets":[1],"controls":[]}
112+
],"title":"Pseudo measurement (collapse by interference)"}
113+
],"circuit":[
114+
{"type":"F","time":0,"targets":[0],"controls":[]},
115+
{"type":"TEL","time":9,"targets":[0,1,2],"controls":[]},
116+
{"type":"MEAS","time":18,"targets":[0,1],"controls":[]}
117+
],"qubits":3,"input":[0,0,0]
118+
}
119+
120+
};

‎js/gate.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
module.exports = class Gate {
2+
3+
constructor(type, time, targets, controls) {
4+
this.type = type;
5+
this.time = time;
6+
this.targets = targets;
7+
this.controls = controls || [];
8+
const qubits = this.targets.concat(this.controls);
9+
this.range = [
10+
Math.min.apply(Math, qubits),
11+
Math.max.apply(Math, qubits),
12+
];
13+
}
14+
15+
addControl(control) {
16+
if (this.controls.indexOf(control) < 0 && this.targets.indexOf(control) < 0) {
17+
this.controls.push(control);
18+
this.range[0] = Math.min(this.range[0], control);
19+
this.range[1] = Math.max(this.range[1], control);
20+
}
21+
}
22+
23+
removeControl(control) {
24+
this.controls.splice(this.controls.indexOf(control), 1);
25+
const qubits = this.targets.concat(this.controls);
26+
this.range = [
27+
Math.min.apply(Math, qubits),
28+
Math.max.apply(Math, qubits),
29+
];
30+
};
31+
32+
touching(time, qubit) {
33+
if (time != this.time) {
34+
return false;
35+
}
36+
return this.range[0] <= qubit && qubit <= this.range[1];
37+
}
38+
39+
render(draw) {
40+
const x = this.time * 40 + 20;
41+
const y1 = this.targets[0] * 40 + 20;
42+
for (let i = 0; i < this.controls.length; i++) {
43+
const y2 = this.controls[i] * 40 + 20;
44+
draw.control(x, y2);
45+
draw.wire(x, y1, y2);
46+
}
47+
if (this.type.name == 'x' && this.controls.length > 0) {
48+
draw.not(x, y1);
49+
} else if (this.type.name == 'swap') {
50+
const y2 = this.targets[1] * 40 + 20;
51+
draw.swap(x, y1);
52+
draw.wire(x, y1, y2);
53+
draw.swap(x, y2);
54+
} else {
55+
draw.gate(x, y1, this.targets.length, this.type.name.toUpperCase());
56+
}
57+
}
58+
59+
}

‎js/libs/numeric.js

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎js/libs/processing.js

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎js/main.js

+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
/*
2+
TODO: More of the DOM specific stuff needs to be moved to the editor. Such as
3+
the toolbar construction.
4+
*/
5+
6+
const Application = require('./application');
7+
const examples = require('./examples');
8+
9+
const displayAmplitudes = (nqubits, amplitudes) => {
10+
const table = document.querySelector('#amplitudes');
11+
table.innerHTML = '';
12+
const hideBtn = document.querySelector('#hide-impossible');
13+
const hide = hideBtn.innerHTML !== '(hide impossible)';
14+
document.querySelector('#amplitudes-container').style.display = 'block';
15+
for (let i = 0; i < amplitudes.x.length; i++) {
16+
let amplitude = '';
17+
let state = '';
18+
for (let j = 0; j < nqubits; j++) {
19+
state = ((i & (1 << j)) >> j) + state;
20+
}
21+
amplitude += amplitudes.x[i].toFixed(8);
22+
amplitude += amplitudes.y[i] < 0 ? '-' : '+';
23+
amplitude += Math.abs(amplitudes.y[i]).toFixed(8) + 'i';
24+
const row = document.createElement('tr');
25+
let prob = Math.pow(amplitudes.x[i], 2);
26+
prob += Math.pow(amplitudes.y[i], 2);
27+
if (prob < numeric.epsilon) {
28+
if (hide) {
29+
continue;
30+
} else {
31+
row.style.color = '#ccc';
32+
}
33+
}
34+
const probability = (prob * 100).toFixed(4) + '%';
35+
row.innerHTML = `
36+
<td style="text-align: right">${amplitude}</td>
37+
<td>|${state}></td>
38+
<td style="text-indent: 20px">${probability}</td>
39+
`;
40+
table.appendChild(row);
41+
}
42+
}
43+
44+
window.onload = () => {
45+
document.querySelector('#toolbar').onselectstart = evt => false;
46+
const canvas = document.getElementById('canvas');
47+
const app = new Application(canvas, 2);
48+
const editor = app.editor;
49+
50+
const hideBtn = document.querySelector('#hide-impossible');
51+
hideBtn.onclick = evt => {
52+
evt.preventDefault();
53+
const hide = '(hide impossible)';
54+
const show = '(show all)';
55+
hideBtn.innerHTML = hideBtn.innerHTML == hide ? show : hide;
56+
document.querySelector('#evaluate').click();
57+
};
58+
59+
document.querySelector('#reset').onclick = evt => {
60+
evt.preventDefault();
61+
const ok = confirm('Clear entire circuit?');
62+
if (ok) {
63+
app.circuit.gates = [];
64+
editor.render();
65+
}
66+
};
67+
68+
document.querySelector('#evaluate').onclick = evt => {
69+
evt.preventDefault();
70+
app.circuit.gates.sort((a, b) => a.time - b.time);
71+
const size = Math.pow(2, app.circuit.nqubits);
72+
const amplitudes = new numeric.T(numeric.rep([size], 0), numeric.rep([size], 0));
73+
const state = editor.input.join('');
74+
amplitudes.x[parseInt(state, 2)] = 1;
75+
app.applyCircuit(app.circuit, amplitudes, amplitudes => {
76+
displayAmplitudes(app.circuit.nqubits, amplitudes.div(amplitudes.norm2()))
77+
});
78+
};
79+
80+
document.body.onkeydown = evt => {
81+
// Catch hotkeys
82+
if (evt.which == 'S'.charCodeAt(0) && evt.ctrlKey) {
83+
evt.preventDefault();
84+
document.querySelector('#compile').click();
85+
} else if (evt.which == 13) {
86+
evt.preventDefault();
87+
document.querySelector('#evaluate').click();
88+
}
89+
};
90+
91+
document.querySelector('#compile').onclick = evt => {
92+
evt.preventDefault();
93+
app.circuit.gates.sort((a, b) => a.time - b.time);
94+
const size = Math.pow(2, app.circuit.nqubits);
95+
const U = new numeric.T(numeric.identity(size), numeric.rep([size, size], 0));
96+
app.applyCircuit(app.circuit, U, U => {
97+
const name = prompt('Name of gate:', 'F');
98+
if (name) {
99+
if (app.workspace.gates[name]) {
100+
app.workspace.gates[name].matrix = U;
101+
app.workspace.gates[name].circuit = app.circuit.copy();
102+
app.workspace.gates[name].nqubits = app.circuit.nqubits;
103+
app.workspace.gates[name].input = app.editor.input;
104+
} else {
105+
app.workspace.addGate({
106+
name: name,
107+
qubits: app.circuit.nqubits,
108+
matrix: U,
109+
circuit: app.circuit.copy(),
110+
input: app.editor.input
111+
});
112+
}
113+
}
114+
});
115+
};
116+
117+
document.querySelector('#exportImage').onclick = evt => {
118+
evt.preventDefault();
119+
const oldlength = editor.length;
120+
const times = app.circuit.gates.map(gate => gate.time);
121+
editor.resize(app.circuit.nqubits, Math.max.apply(Math, times) + 1);
122+
window.open(editor.draw.canvas.toDataURL("image/png"));
123+
editor.resize(app.circuit.nqubits, oldlength);
124+
};
125+
126+
document.querySelector('#exportMatrix').onclick = evt => {
127+
evt.preventDefault();
128+
app.circuit.gates.sort((a, b) => a.time - b.time);
129+
const size = Math.pow(2, app.circuit.nqubits);
130+
const U = new numeric.T(numeric.identity(size), numeric.rep([size, size], 0));
131+
app.applyCircuit(app.circuit, U, U => {
132+
const child = window.open('', 'matrix.csv', ',resizable=yes,scrollbars=yes,menubar=yes,toolbar=yes,titlebar=yes,hotkeys=yes,status=1,dependent=no');
133+
for (let i = 0; i < U.x.length; i++) {
134+
const row = [];
135+
for (let j = 0; j < U.x[i].length; j++) {
136+
row.push(U.x[i][j].toFixed(16) + '+' + U.y[i][j].toFixed(16) + 'i');
137+
}
138+
child.document.write(row.join(',') + '<br>');
139+
}
140+
});
141+
};
142+
143+
document.querySelector('#importJSON').onclick = evt => {
144+
evt.preventDefault();
145+
const json = prompt('Paste JSON:');
146+
app.loadWorkspace(JSON.parse(json));
147+
};
148+
149+
document.querySelector('#exportJSON').onclick = evt => {
150+
evt.preventDefault();
151+
app.circuit.gates.sort((a, b) => a.time - b.time);
152+
const gates = [];
153+
document.querySelectorAll('#toolbar .user div.gate').forEach(gate => {
154+
const name = gate.dataset.type;
155+
const type = app.workspace.gates[name];
156+
gates.push({
157+
name: name,
158+
qubits: type.circuit.nqubits,
159+
circuit: type.circuit.toJSON(),
160+
title: ''
161+
});
162+
});
163+
const json = JSON.stringify({
164+
gates: gates,
165+
circuit: app.circuit.toJSON(),
166+
qubits: app.circuit.nqubits,
167+
input: editor.input
168+
});
169+
const a = document.createElement('a');
170+
a.download = 'circuit.json';
171+
a.innerHTML = 'circuit.json';
172+
a.href = 'data:text/javascript,' + encodeURI(json);
173+
document.body.appendChild(a);
174+
a.click();
175+
document.body.removeChild(a);
176+
};
177+
178+
const resize = size => {
179+
document.querySelector('#nqubits > span').innerHTML = 'Qubits: ' + size;
180+
const newGates = app.circuit.gates.filter(gate => {
181+
return gate.range[1] < size;
182+
});
183+
if (newGates.length < app.circuit.gates.length) {
184+
const count = app.circuit.gates.length - newGates.length;
185+
const ok = confirm('Resizing will remove ' + count + ' gates. Resize anyway?')
186+
if (ok) {
187+
app.circuit.gates = newGates;
188+
editor.resize(size, editor.length);
189+
}
190+
} else {
191+
editor.resize(size, editor.length);
192+
}
193+
};
194+
195+
const nqubitsUl = document.querySelector('#nqubits > ul');
196+
for (let i = 1; i < 11; i++) {
197+
const li = document.createElement('li');
198+
const a = document.createElement('a');
199+
a.href = '#';
200+
a.innerHTML = i;
201+
a.onclick = evt => {
202+
evt.preventDefault();
203+
resize(i);
204+
};
205+
li.appendChild(a);
206+
nqubitsUl.appendChild(li);
207+
if (i == 2) {
208+
a.click();
209+
}
210+
}
211+
212+
const getUrlVars = () => {
213+
const vars = [];
214+
const location = window.location.href;
215+
const hashes = location.slice(location.indexOf('?') + 1).split('&');
216+
for(let i = 0; i < hashes.length; i++) {
217+
const hash = hashes[i].split('=');
218+
vars.push(hash[0]);
219+
vars[hash[0]] = decodeURI(hash[1]);
220+
}
221+
return vars;
222+
}
223+
224+
const EXAMPLES = [
225+
["Toffoli", examples.TOFFOLI],
226+
["Bell State", examples.BELL_STATE],
227+
["2 Qubit QFT", examples.QFT2],
228+
["4 Qubit QFT", examples.QFT4],
229+
["Grover's Algorithm", examples.GROVERS_ALGORITHM],
230+
["Quantum Teleportation", examples.TELEPORTATION],
231+
];
232+
const examplesEl = document.querySelector('#examples');
233+
EXAMPLES.forEach((example, i) => {
234+
const name = example[0];
235+
const json = example[1];
236+
const a = document.createElement('a');
237+
a.href = '#';
238+
a.appendChild(document.createTextNode(name));
239+
a.onclick = evt => {
240+
evt.preventDefault();
241+
open('?example=' + example[0]);
242+
};
243+
if (getUrlVars().example == name) {
244+
app.loadWorkspace(json);
245+
}
246+
const li = document.createElement('li');
247+
li.appendChild(a);
248+
examplesEl.appendChild(li);
249+
});
250+
251+
document.querySelector('#about').onclick = evt => {
252+
document.querySelector('#modal').style.display = 'block';
253+
};
254+
255+
document.querySelector('#modal').onclick = evt => {
256+
document.querySelector('#modal').style.display = 'none';
257+
};
258+
259+
document.querySelector('#modal > div').onclick = evt => {
260+
evt.preventDefault();
261+
evt.stopPropagation();
262+
};
263+
264+
};

‎js/quantum.js

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
const quantum = module.exports;
2+
3+
/*
4+
Return version of U controlled by first qubit.
5+
*/
6+
quantum.controlled = U => {
7+
const m = U.x.length;
8+
const Mx = numeric.identity(m * 2);
9+
const My = numeric.rep([m * 2, m * 2], 0);
10+
for (let i = 0; i < m; i++) {
11+
for (let j = 0; j < m; j++) {
12+
Mx[i + m][j + m] = U.x[i][j];
13+
My[i + m][j + m] = U.y[i][j];
14+
}
15+
}
16+
return new numeric.T(Mx, My);
17+
};
18+
19+
/*
20+
Return transformation over entire nqubit register that applies U to
21+
specified qubits (in order given).
22+
Algorithm from Lee Spector's "Automatic Quantum Computer Programming"
23+
*/
24+
quantum.expandMatrix = (nqubits, U, qubits) => {
25+
const _qubits = [];
26+
const n = Math.pow(2, nqubits);
27+
qubits = qubits.slice(0);
28+
for (let i = 0; i < qubits.length; i++) {
29+
qubits[i] = (nqubits - 1) - qubits[i];
30+
}
31+
qubits.reverse();
32+
for (let i = 0; i < nqubits; i++) {
33+
if (qubits.indexOf(i) == -1) {
34+
_qubits.push(i);
35+
}
36+
}
37+
const X = numeric.rep([n, n], 0);
38+
const Y = numeric.rep([n, n], 0);
39+
let i = n;
40+
while (i--) {
41+
let j = n;
42+
while (j--) {
43+
let bitsEqual = true;
44+
let k = _qubits.length;
45+
while (k--) {
46+
if ((i & (1 << _qubits[k])) != (j & (1 << _qubits[k]))) {
47+
bitsEqual = false;
48+
break;
49+
}
50+
}
51+
if (bitsEqual) {
52+
let istar = 0;
53+
let jstar = 0;
54+
let k = qubits.length;
55+
while (k--) {
56+
const q = qubits[k];
57+
istar |= ((i & (1 << q)) >> q) << k;
58+
jstar |= ((j & (1 << q)) >> q) << k;
59+
}
60+
X[i][j] = U.x[istar][jstar];
61+
Y[i][j] = U.y[istar][jstar];
62+
}
63+
}
64+
}
65+
return new numeric.T(X, Y);
66+
};
67+
68+
quantum.h = new numeric.T(
69+
numeric.div([[1, 1], [1, -1]], Math.sqrt(2)),
70+
numeric.rep([2, 2], 0)
71+
);
72+
73+
quantum.x = new numeric.T(
74+
[[0, 1], [1, 0]],
75+
numeric.rep([2, 2], 0)
76+
);
77+
78+
quantum.y = new numeric.T(
79+
numeric.rep([2, 2], 0),
80+
[[0, -1], [1, 0]]
81+
);
82+
83+
quantum.z = new numeric.T(
84+
[[1, 0], [0, -1]],
85+
numeric.rep([2, 2], 0)
86+
);
87+
88+
quantum.s = new numeric.T(
89+
[[1, 0], [0, 0]],
90+
[[0, 0], [0, 1]]
91+
);
92+
93+
const makeR = theta => {
94+
const x = Math.cos(theta);
95+
const y = Math.sin(theta);
96+
return new numeric.T([[1, 0], [0, x]], [[0, 0], [0, y]]);
97+
}
98+
99+
quantum.r2 = makeR(Math.PI / 2);
100+
quantum.r4 = makeR(Math.PI / 4);
101+
quantum.r8 = makeR(Math.PI / 8);
102+
103+
quantum.swap = new numeric.T(
104+
[
105+
[1, 0, 0, 0],
106+
[0, 0, 1, 0],
107+
[0, 1, 0, 0],
108+
[0, 0, 0, 1]
109+
],
110+
numeric.rep([4, 4], 0)
111+
);
112+
113+
/*
114+
Return Quantum Fourier Transform matrix of an nqubit register operating
115+
on specified qubits.
116+
*/
117+
quantum.qft = (nqubits) => {
118+
const n = Math.pow(2, nqubits);
119+
const wtheta = (2 * Math.PI) / n;
120+
const x = numeric.rep([n, n], 0);
121+
const y = numeric.rep([n, n], 0);
122+
for (let i = 0; i < n; i++) {
123+
for (let j = 0; j < n; j++) {
124+
x[i][j] = Math.cos(i * j * wtheta);
125+
y[i][j] = Math.sin(i * j * wtheta);
126+
}
127+
}
128+
return new numeric.T(x, y).div(Math.sqrt(n));
129+
};
130+
131+
quantum.srn = new numeric.T(
132+
numeric.div([[1, -1], [1, 1]], Math.sqrt(2)),
133+
numeric.rep([2, 2], 0)
134+
);

‎js/workspace.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const quantum = require('./quantum');
2+
3+
module.exports = class Workspace {
4+
5+
constructor(app) {
6+
this.app = app;
7+
this.gates = {};
8+
this.installStandardGates();
9+
}
10+
11+
installStandardGates() {
12+
this.addGate({name: 'h', qubits: 1, matrix: quantum.h, title: 'Hadamard'}, true);
13+
this.addGate({name: 'x', qubits: 1, matrix: quantum.x, title: 'Pauli-X'}, true);
14+
this.addGate({name: 'y', qubits: 1, matrix: quantum.y, title: 'Pauli-Y'}, true);
15+
this.addGate({name: 'z', qubits: 1, matrix: quantum.z, title: 'Pauli-Z'}, true);
16+
this.addGate({name: 's', qubits: 1, matrix: quantum.s, title: 'Phase Gate'}, true);
17+
this.addGate({name: 't', qubits: 1, matrix: quantum.r4, title: 'Same as R4'}, true);
18+
this.addGate({name: 'cnot', qubits: 2, matrix: quantum.cnot, title: 'Controlled Not'}, true);
19+
this.addGate({name: 'control'}, true);
20+
this.addGate({name: 'swap', qubits: 2, matrix: quantum.swap, title: 'Swap'}, true);
21+
this.addGate({name: 'r2', qubits: 1, matrix: quantum.r2, title: 'Pi/2 Phase Rotatation'}, true);
22+
this.addGate({name: 'r4', qubits: 1, matrix: quantum.r4, title: 'Pi/4 Phase Rotatation'}, true);
23+
this.addGate({name: 'r8', qubits: 1, matrix: quantum.r8, title: 'Pi/8 Phase Rotatation'}, true);
24+
this.addGate({name: 'qft', qubits: Infinity, fn: quantum.qft, title: 'Quantum Fourier Transform'}, true);
25+
this.addGate({name: 'srn', qubits: 1, matrix: quantum.srn, title: 'Sqrt(Not)'}, true);
26+
}
27+
28+
addGate(ops, std) {
29+
this.gates[ops.name] = {
30+
name: ops.name,
31+
qubits: ops.qubits,
32+
matrix: ops.matrix,
33+
circuit: ops.circuit,
34+
fn: ops.fn,
35+
title: ops.title,
36+
input: ops.input
37+
};
38+
this.app.addToolbarButton(std ? 'std' : 'user', ops.name, ops.title);
39+
}
40+
41+
}
42+

‎package.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "quantum-circuit-simulator",
3+
"devDependencies": {
4+
"browserify": "latest"
5+
},
6+
"scripts": {
7+
"build": "browserify js/main.js -o js/bundle.js"
8+
}
9+
}

0 commit comments

Comments
 (0)
Please sign in to comment.