diff --git a/Q-Circuit.html b/Q-Circuit.html
index a64b2b2..c5c5e77 100644
--- a/Q-Circuit.html
+++ b/Q-Circuit.html
@@ -40,20 +40,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
+
diff --git a/Q-ComplexNumber.html b/Q-ComplexNumber.html
index 011efaf..144e3a1 100644
--- a/Q-ComplexNumber.html
+++ b/Q-ComplexNumber.html
@@ -40,20 +40,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
+
@@ -1096,17 +1089,17 @@
Maths (destructive)
-// console.log( '\n\nQ.ComplexNumber\n\n', Q.ComplexNumber.help(), '\n\n' )
+// console.log( '\n\nComplexNumber\n\n', ComplexNumber.help(), '\n\n' )
var
-cat = new Q.ComplexNumber( 1, 2 ),
-dog = new Q.ComplexNumber( 3, -4 ),
-i = new Q.ComplexNumber( 0, 1 )
+cat = new ComplexNumber( 1, 2 ),
+dog = new ComplexNumber( 3, -4 ),
+i = new ComplexNumber( 0, 1 )
var
-ape = new Q.ComplexNumber(),
-bee = new Q.ComplexNumber( 1 ),
-elk = new Q.ComplexNumber( 1, 2 ),
+ape = new ComplexNumber(),
+bee = new ComplexNumber( 1 ),
+elk = new ComplexNumber( 1, 2 ),
fox = elk.clone()// We’re avoiding a warning here: new Q .ComplexNumber( cat ),
diff --git a/Q-Gate.html b/Q-Gate.html
index 172620b..0e799c8 100644
--- a/Q-Gate.html
+++ b/Q-Gate.html
@@ -40,20 +40,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
+
@@ -1265,18 +1258,18 @@ Prototype properties
-var gup = new Q.Gate({
+var gup = new Gate({
symbol: 'G',
name: 'Gup',
nameCss: 'gup',
- matrix: Q.Gate.PAULI_X.matrix
+ matrix: Gate.PAULI_X.matrix
})
-var fox = Q.Gate.PHASE.clone({ symbol: 'F', phi: Math.PI })
+var fox = Gate.PHASE.clone({ symbol: 'F', phi: Math.PI })
diff --git a/Q-Matrix.html b/Q-Matrix.html
index 5d80b14..946d9ce 100644
--- a/Q-Matrix.html
+++ b/Q-Matrix.html
@@ -40,20 +40,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
+
@@ -1250,7 +1243,7 @@ Maths operations (destructive)
// First, what do the docs have to say?
-// console.log( '\n\nQ.Matrix\n\n', Q.Matrix.help(), '\n\n' )
+// console.log( '\n\nMatrix\n\n', Matrix.help(), '\n\n' )
// We’re going to use `var` here instead of `let` or `const`
@@ -1258,7 +1251,7 @@ Maths operations (destructive)
-var a = new Q.Matrix(
+var a = new Matrix(
[ 1, 2, 3 ],
[ 4, 5, 6 ]
@@ -1283,22 +1276,22 @@ Maths operations (destructive)
/*
-console.log( '\n\nQ.Matrix\n', Q.extractDocumentation( Q.Matrix ), '\n\n' )
-console.log( '\n\nQ.Matrix.prototype.multiply\n', Q.extractDocumentation( Q.Matrix.prototype.multiply ), '\n\n' )
+console.log( '\n\nMatrix\n', Q.extractDocumentation( Matrix ), '\n\n' )
+console.log( '\n\nMatrix.prototype.multiply\n', Q.extractDocumentation( Matrix.prototype.multiply ), '\n\n' )
-console.log( 'Q.Matrix.IDENTITY_2X2', Q.Matrix.IDENTITY_2X2 )
-console.log( 'Q.Matrix.IDENTITY_3X3', Q.Matrix.IDENTITY_3X3 )
-console.log( 'Q.Matrix.IDENTITY_4X4', Q.Matrix.IDENTITY_4X4 )
+console.log( 'Matrix.IDENTITY_2X2', Matrix.IDENTITY_2X2 )
+console.log( 'Matrix.IDENTITY_3X3', Matrix.IDENTITY_3X3 )
+console.log( 'Matrix.IDENTITY_4X4', Matrix.IDENTITY_4X4 )
-console.log( 'Q.Matrix.CNOT', Q.Matrix.CNOT )
-console.log( '\nQ.Matrix.CNOT.toHtml()', Q.Matrix.CNOT.toHTML(), '\n\n' )
-console.log( '\nQ.Matrix.TEST_MAP_9X9.toCSV()', Q.Matrix.TEST_MAP_9X9.toCSV(), '\n\n' )
-console.log( '\nQ.Matrix.TEST_MAP_9X9.toTsv()', Q.Matrix.TEST_MAP_9X9.toTsv(), '\n\n' )
+console.log( 'Matrix.CNOT', Matrix.CNOT )
+console.log( '\nMatrix.CNOT.toHtml()', Matrix.CNOT.toHTML(), '\n\n' )
+console.log( '\nMatrix.TEST_MAP_9X9.toCSV()', Matrix.TEST_MAP_9X9.toCSV(), '\n\n' )
+console.log( '\nMatrix.TEST_MAP_9X9.toTsv()', Matrix.TEST_MAP_9X9.toTsv(), '\n\n' )
-console.log( 'How many matrices have we created?', Q.Matrix.index )
+console.log( 'How many matrices have we created?', Matrix.index )
*/
@@ -1357,8 +1350,8 @@ Maths operations (destructive)
[ 3, 3, 3 ])
-console.log( 'Q.Matrix.IDENTITY_2X2.multiply( e ).toTsv()', Q.Matrix.IDENTITY_2X2.multiply( e ))
-console.log( 'Q.Matrix.IDENTITY_3X3.multiply( c ).toTsv()', Q.Matrix.IDENTITY_3X3.multiply( c ))
+console.log( 'Matrix.IDENTITY_2X2.multiply( e ).toTsv()', Matrix.IDENTITY_2X2.multiply( e ))
+console.log( 'Matrix.IDENTITY_3X3.multiply( c ).toTsv()', Matrix.IDENTITY_3X3.multiply( c ))
*/
@@ -1383,14 +1376,14 @@ Maths operations (destructive)
// Import and export formats.
-var csv = Q.Matrix.fromCsv(`
+var csv = Matrix.fromCsv(`
1, 2, 3
4, 5, 6
7, 8, 9`)
// console.log( 'Matrix from CSV', csv.toTsv(), '\n\n' )
-var tsv = Q.Matrix.fromTsv(`1 2 3
+var tsv = Matrix.fromTsv(`1 2 3
4 5 6
7 8 9`)
// console.log( 'Matrix from TSV', tsv.toTsv(), '\n\n' )
@@ -1415,7 +1408,7 @@ Maths operations (destructive)
`
-// console.log( 'Matrix from HTML', Q.Matrix.fromHtml( html ).toTsv(), '\n\n' )
+// console.log( 'Matrix from HTML', Matrix.fromHtml( html ).toTsv(), '\n\n' )
diff --git a/Q-Qubit.html b/Q-Qubit.html
index 01f6e18..5df02cd 100644
--- a/Q-Qubit.html
+++ b/Q-Qubit.html
@@ -40,20 +40,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
+
@@ -61,7 +54,6 @@
-
@@ -1885,7 +1877,7 @@ Destructive methods
// Examples created in the docs:
-var fox = new Q.Qubit( 1, 0 )
+var fox = new Qubit( 1, 0 )
@@ -1958,7 +1950,7 @@ Destructive methods
camera.add( light )
scene.add( new THREE.AmbientLight( 0xFFFFFF, 0.7 ))
-const blochSphere = new Q.BlochSphere( function(){
+const blochSphere = new BlochSphere( function(){
document
.getElementById( 'bloch-theta' )
@@ -2033,7 +2025,7 @@ Destructive methods
'HDLARV'.split( '' ).forEach( function( symbol ){
const
- qubit = Q.Qubit.findBySymbol( symbol ),
+ qubit = Qubit.findBySymbol( symbol ),
qubitElement = document.createElement( 'div' ),
qubitGutsElement = document.createElement( 'div' )
@@ -2069,7 +2061,7 @@ Destructive methods
blochSphere.group.rotation.x = Math.PI * 0.3
blochSphere.group.rotation.y = Math.PI / -4
-selectBlochQubit( Q.Qubit.HORIZONTAL )
+selectBlochQubit( Qubit.HORIZONTAL )
render()
diff --git a/Q.html b/Q.html
index 3dc569f..b00ba31 100644
--- a/Q.html
+++ b/Q.html
@@ -40,20 +40,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
+
diff --git a/build/bundle.css b/build/bundle.css
new file mode 100644
index 0000000..ba23b8d
--- /dev/null
+++ b/build/bundle.css
@@ -0,0 +1,2232 @@
+/*
+
+ Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+
+*/
+@charset "utf-8";
+
+
+
+
+/*
+
+ This file is in the process of being separated
+ in to “essential global Q.js styles” which will
+ remain here in Q.css, and “documentation-specific”
+ styles which will be removed from here and placed
+ within the /other/documentation.css file instead.
+
+ The goal is for a developer to be able to place
+ this Q.css file into their own web app with little
+ to no interference with their app. All variables
+ and styles here will ultimately be prefaced with
+ a capital Q, eg. --Q-color-base, or .Q-text-input
+ and so on.
+
+
+*/
+
+
+
+
+svg, :root {
+
+
+
+ /**************/
+ /* */
+ /* Colors */
+ /* */
+ /**************/
+
+
+ /* Base color (blue) */
+
+ --Q-color-base-hue: 210;
+ --Q-color-base-saturation: 85%;
+ --Q-color-base-lightness: 40%;
+
+
+ /* Red */
+
+ --Q-color-red-hue: calc( var( --Q-color-base-hue ) + 180 - 30 );
+ --Q-color-red-saturation: 85%;
+ --Q-color-red-lightness: 45%;
+ --Q-color-red: hsl(
+
+ var( --Q-color-red-hue ),
+ var( --Q-color-red-saturation ),
+ var( --Q-color-red-lightness )
+ );
+
+
+ /* Orange */
+
+ --Q-color-orange-hue: calc( var( --Q-color-base-hue ) + 180 - 15 );
+ --Q-color-orange-saturation: 85%;
+ --Q-color-orange-lightness: 50%;
+ --Q-color-orange: hsl(
+
+ var( --Q-color-orange-hue ),
+ var( --Q-color-orange-saturation ),
+ var( --Q-color-orange-lightness )
+ );
+
+
+ /* Yellow */
+
+ --Q-color-yellow-hue: calc( var( --Q-color-base-hue ) + 180 + 15 );
+ --Q-color-yellow-saturation: 90%;
+ --Q-color-yellow-lightness: 50%;
+ --Q-color-yellow: hsl(
+
+ var( --Q-color-yellow-hue ),
+ var( --Q-color-yellow-saturation ),
+ var( --Q-color-yellow-lightness )
+ );
+
+
+ /* Green */
+
+ --Q-color-green-hue: calc( var( --Q-color-base-hue ) + 180 + 60 );
+ --Q-color-green-saturation: 80%;
+ --Q-color-green-lightness: 35%;
+ --Q-color-green: hsl(
+
+ var( --Q-color-green-hue ),
+ var( --Q-color-green-saturation ),
+ var( --Q-color-green-lightness )
+ );
+
+
+ /* Blue */
+
+ --Q-color-blue-hue: var( --Q-color-base-hue );
+ --Q-color-blue-saturation: var( --Q-color-base-saturation );
+ --Q-color-blue-lightness: var( --Q-color-base-lightness );
+ --Q-color-blue: hsl(
+
+ var( --Q-color-blue-hue ),
+ var( --Q-color-blue-saturation ),
+ var( --Q-color-blue-lightness )
+ );
+
+
+ /* Grayscale */
+
+ --Q-color-white: #FFFFFF;
+ --Q-color-chalk: #F9F9F9;
+ --Q-color-newsprint: #F3F3F3;
+ --Q-color-titanium: #CCCCCC;
+ --Q-color-slate: #777777;
+ --Q-color-charcoal: #333333;
+ --Q-color-black: #000000;
+
+
+ /* Background */
+
+ --Q-color-background-hue: var( --Q-color-base-hue );
+ --Q-color-background-saturation: 15%;
+ --Q-color-background-lightness: 98%;
+ --Q-color-background: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ var( --Q-color-background-lightness )
+ );
+ /*--Q-color-background: white;*/
+
+
+ /* Misc */
+
+ --Q-text-color: hsl(
+
+ var( --Q-color-base-hue ),
+ 5%,
+ 35%
+ );
+ --Q-text-code-comment-color: rgba( 0, 0, 0, 0.4 );
+ --Q-text-code-output-color: rgba( 0, 0, 0, 1 );
+
+ --Q-selection-color: var( --Q-color-black );
+ --Q-selection-background-color: var( --Q-color-yellow );
+
+ --Q-hyperlink-internal-color: var( --Q-color-blue );
+ --Q-hyperlink-external-color: var( --Q-text-color );
+
+ --Q-background-callout-color: var( --Q-color-white );
+
+ --Q-svg-fill-color: var( --Q-text-color );
+
+
+
+
+ /* Fonts */
+
+ --Q-font-family-serif: 'Source Serif Pro', 'Roboto Slab', 'Georgia', serif;
+ --Q-font-family-sans: 'SF Pro Text', system-ui, -apple-system, 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
+ --Q-font-family-mono: 'Roboto Mono', 'Source Code Pro', 'Menlo', 'Courier New', monospace;
+ --Q-font-family-symbols: 'Georgia', serif;
+}
+
+
+
+
+ /*******************/
+ /* */
+ /* Interactive */
+ /* */
+/*******************/
+
+
+.Q-input,
+.Q-circuit-text-input {
+
+ margin: 1.5rem 0 0 0 !important;
+ outline: none !important;
+ border: none !important;
+ border-radius: 1.2rem !important;
+ box-shadow:
+ 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.15 ) inset,
+ -0.2rem -0.2rem 0.2rem rgba( 255, 255, 255, 1 ) inset;
+
+ background: linear-gradient(
+
+ 0.375turn,
+ rgba( 255, 255, 255, 1.0 ),
+ rgba( 255, 255, 255, 0.2 )
+ ) !important;
+}
+.Q-input,
+.Q-circuit-text-input {
+
+ padding: 1.5rem !important;
+ color: #555 !important;
+ font-size: 0.9rem !important;
+ line-height: 1.2rem !important;
+}
+
+
+
+.Q-circuit-text-input {
+
+ /*min-width: 18rem;*/
+ width: 100%;
+ min-height: 8rem;
+ /*margin: 1rem 0 2rem 0;*/
+ margin: 1rem 0 0 0;
+ border: 1px solid var( --Q-color-blue );
+ border-radius: 0.5rem;
+ background-color: var( --Q-color-chalk );
+ padding: 1rem 0 0 2rem;
+ color: var( --Q-color-blue );
+ font-family: var( --Q-font-family-mono );
+ font-size: 1.0rem;
+ line-height: 1.2rem;
+ white-space: pre;
+ word-wrap: normal;/* OMFG, iOS you make me sad. */
+}
+
+
+
+
+
+
+.Q-button {
+
+ position: relative;
+ text-align: right;
+ margin: 0.5rem 1rem 0 0;
+ border-radius: 3rem;
+ box-shadow:
+ -0.1rem -0.1rem 0 rgba( 255, 255, 255, 1 ),
+ 0.1rem 0.1rem 0.2rem rgba( 0, 0, 0, 0.3 );
+ height: 3rem;
+ background:
+ var( --Q-color-blue )
+ linear-gradient(
+
+ 0.4turn,
+ rgba( 255, 255, 255, 0.2 ),
+ rgba( 0, 0, 0, 0.08 )
+ );
+ padding: 0.8rem 1.8rem;
+ color: var( --Q-color-white );
+ font-family: var( --Q-font-family-sans );
+ font-size: 1rem;
+ line-height: 1rem;
+ font-weight: 500;
+ letter-spacing: 0;
+ text-shadow: -1px -1px 0 rgba( 0, 0, 0, 0.1 );
+ cursor: pointer;
+}
+.Q-button:hover {
+
+ background:
+ hsl(
+
+ var( --Q-color-blue-hue ),
+ var( --Q-color-blue-saturation ),
+ calc( var( --Q-color-blue-lightness ) * 1.2 )
+ )
+ linear-gradient(
+
+ 0.4turn,
+ rgba( 255, 255, 255, 0.2 ),
+ rgba( 0, 0, 0, 0.08 )
+ );
+}
+.Q-button:focus {
+
+ margin-top: 0.7rem;
+ margin-bottom: -0.2rem;
+ margin-right: 0.9rem;
+ outline: none;
+ box-shadow:
+ -0.1rem -0.1rem 0 rgba( 255, 255, 255, 1 ) inset,
+ 0.1rem 0.1rem 0.2rem rgba( 0, 0, 0, 0.3 ) inset;
+ background:
+ var( --Q-color-blue )
+ linear-gradient(
+
+ 0.4turn,
+ rgba( 0, 0, 0, 0.08 ),
+ rgba( 255, 255, 255, 0.2 )
+ );
+}
+.Q-button[disabled] {
+
+ box-shadow:
+ -0.1rem -0.1rem 0 rgba( 255, 255, 255, 1 ),
+ 0.1rem 0.1rem 0.2rem rgba( 0, 0, 0, 0.3 );
+ background:
+ var( --Q-color-background )
+ linear-gradient(
+
+ 0.45turn,
+ rgba( 255, 255, 255, 0.1 ),
+ rgba( 0, 0, 0, 0.05 )
+ );
+ color: rgba( 0, 0, 0, 0.3 );
+ text-shadow: 1px 1px 0 rgba( 255, 255, 255, 1 );
+ cursor: default;
+}
+
+
+
+
+
+
+/*
+
+ The below still need to be prefaced with “Q-”
+ and for the HTML pages to be updated accordingly.
+
+*/
+
+
+
+
+
+
+ /*************/
+ /* */
+ /* Maths */
+ /* */
+/*************/
+
+
+.maths {
+
+ max-width: 100%;
+ overflow-x: auto;
+ font-family: var( --Q-font-family-sans );
+}
+dd .maths {
+
+ margin-top: 0;
+ margin-left: 0;
+}
+
+
+
+
+.symbol {
+
+ font-size: 1.1em;
+ padding: 0 0.1em;
+ font-family: var( --Q-font-family-symbols );
+ font-style: italic;
+ font-weight: 900;
+ letter-spacing: 0.05em;
+}
+
+
+
+
+.division {
+
+ display: inline-block;
+ vertical-align: middle;
+ margin: 10px;
+}
+.division td {
+
+ padding: 5px;
+}
+.dividend {
+
+ border-bottom: 1px solid #CCC;
+ text-align: center;
+}
+.divisor {
+
+ text-align: center;
+}
+
+
+
+
+.matrix {
+
+ display: inline-block;
+ vertical-align: middle;
+ position: relative;
+ align: middle;
+ margin: 1em;
+ padding: 1em;
+ font-family: var( --Q-font-family-mono );
+ font-weight: 300;
+ line-height: 1em;
+ text-align: right;
+}
+.matrix td {
+
+ padding: 5px 10px;
+}
+.matrix-bracket-left, .matrix-bracket-right {
+
+ position: absolute;
+ top: 0;
+ width: 5px;
+ height: 100%;
+ border: 1px solid #CCC;
+}
+.matrix-bracket-left {
+
+ left: 0;
+ border-right: none;
+}
+.matrix-bracket-right {
+
+ right: 0;
+ border-left: none;
+}
+/*.matrix.qubit tr:first-child td {
+
+ color: #BBB;
+}*/
+
+
+
+.Q-state-vector,
+.complex-vector {
+
+ font-family: var( --Q-font-family-mono );
+}
+.Q-state-vector.bra::before,
+.complex-vector.bra::before {
+
+ content: '⟨';
+ color: #BBB;
+}
+.Q-state-vector.bra::after,
+.complex-vector.bra::after {
+
+ content: '|';
+ color: #BBB;
+}
+.Q-state-vector.ket::before,
+.complex-vector.ket::before {
+
+ content: '|';
+ color: #BBB;
+}
+.Q-state-vector.ket::after,
+.complex-vector.ket::after {
+
+ content: '⟩';
+ color: #BBB;
+}
+.Q-state-vector.bra + .Q-state-vector.ket::before,
+.complex-vector.bra + .complex-vector.ket::before {
+
+ content: '';
+}
+
+
+
+
+
+
+
+/*
+
+ Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+
+*/
+@charset "utf-8";
+
+
+
+
+
+
+
+
+
+/*
+
+ Z indices:
+
+ Clipboard =100
+ Selected op 10
+ Operation 0
+ Shadow -10
+ Background -20
+
+
+
+
+
+ Circuit
+
+ Menu Moments
+ ╭───────┬───┬───┬───┬───╮
+ │ ≡ ↘ │ 1 │ 2 │ 3 │ + │ Add moment
+ ├───┬───┼───┼───┼───┼───╯
+ R │ 0 │|0⟩│ H │ C0│ X │ -
+ e ├───┼───┼───┼───┼───┤
+ g │ 1 │|0⟩│ I │ C1│ X │ -
+ s ├───┼───┴───┴───┴───┘
+ │ + │ - - - -
+ ╰───╯
+ Add
+ register
+
+
+ Circuit Palette
+
+ ╭───────────────────┬───╮
+ │ H X Y Z S T π M … │ @ │
+ ╰───────────────────┴───╯
+
+
+ Circuit clipboard
+
+ ┌───────────────┐
+ ▟│ ┌───┬───────┐ │
+ █│ │ H │ X#0.0 │ │
+ █│ ├───┼───────┤ │
+ █│ │ I │ X#0.1 │ │
+ █│ └───┴───────┘ │
+ █└───────────────┘
+ ███████████████▛
+
+
+
+ ◢◣
+ ◢■■■■◣
+◢■■■■■■■■◣
+◥■■■■■■■■◤
+ ◥■■■■◤
+ ◥◤
+
+
+ ◢■■■■■■◤
+ ◢◤ ◢◤
+◢■■■■■■◤
+
+
+ ───────────
+ ╲ ╱ ╱ ╱
+ ╳ ╱ ╱
+ ╱ ╲╱ ╱
+ ───────
+
+
+ ─────⦢
+ ╱ ╱
+⦣─────
+
+
+*/
+
+
+
+
+
+
+.Q-circuit,
+.Q-circuit-palette {
+
+ position: relative;
+ width: 100%;
+}
+.Q-circuit-palette {
+
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ line-height: 0;
+}
+.Q-circuit-palette > div {
+
+ display: inline-block;
+ position: relative;
+ width: 4rem;
+ height: 4rem;
+}
+
+
+.Q-circuit {
+
+ margin: 1rem 0 2rem 0;
+ /*border-top: 2px solid hsl( 0, 0%, 50% );*/
+}
+.Q-circuit-board-foreground {
+
+ line-height: 3.85rem;
+ width: auto;
+}
+
+
+
+
+
+
+ /***************/
+ /* */
+ /* Toolbar */
+ /* */
+/***************/
+
+
+.Q-circuit-toolbar {
+
+ display: block;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ margin-bottom: 0.5rem;
+
+ box-sizing: border-box;
+ display: grid;
+ grid-auto-columns: 3.6rem;
+ grid-auto-rows: 3.0rem;
+ grid-auto-flow: column;
+
+}
+.Q-circuit-button {
+
+ position: relative;
+ display: inline-block;
+ /*margin: 0 0.5rem 0.5rem 0;*/
+ width: 3.6rem;
+ height: 3rem;
+/* box-shadow:
+ -0.1rem -0.1rem 0 rgba( 255, 255, 255, 0.8 ),
+ 0.1rem 0.1rem 0.1rem rgba( 0, 0, 0, 0.35 );*/
+
+ border-top: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 100%
+ );
+ border-right: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 90%
+ );
+ border-bottom: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 85%
+ );
+ border-left: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 97%
+ );
+ background: var( --Q-color-background );
+/* background:
+ var( --Q-color-background )
+ linear-gradient(
+
+ 0.4turn,
+
+ rgba( 0, 0, 0, 0.02 ),
+ rgba( 255, 255, 255, 0.1 )
+ );*/
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 30%
+ );
+ text-shadow: 1px 1px 0 rgba( 255, 255, 255, 1 );
+ /*border-radius: 0.5rem;*/
+ /*border-radius: 100%;*/
+ line-height: 2.9rem;
+ text-align: center;
+ cursor: pointer;
+ overflow: hidden;
+ font-weight: 900;
+}
+.Q-circuit-toolbar .Q-circuit-button:first-child {
+
+ border-top-left-radius: 0.5rem;
+ border-bottom-left-radius: 0.5rem;
+}
+.Q-circuit-toolbar .Q-circuit-button:last-child {
+
+ border-top-right-radius: 0.5rem;
+ border-bottom-right-radius: 0.5rem;
+}
+.Q-circuit-locked .Q-circuit-button,
+.Q-circuit-button[Q-disabled] {
+
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 85%
+ );
+ cursor: not-allowed;
+}
+.Q-circuit-locked .Q-circuit-toggle-lock {
+
+ color: inherit;
+ cursor: pointer;
+}
+
+
+
+
+.Q-circuit-board-container {
+
+ position: relative;
+ margin: 0 0 2rem 0;
+ margin: 0;
+ width: 100%;
+ max-height: 60vh;
+ overflow: scroll;
+}
+.Q-circuit-board {
+
+ position: relative;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+/*.Q-circuit-palette,*/
+.Q-circuit-board-foreground,
+.Q-circuit-board-background,
+.Q-circuit-clipboard {
+
+ box-sizing: border-box;
+ display: grid;
+ grid-auto-rows: 4rem;
+ grid-auto-columns: 4rem;
+ grid-auto-flow: column;
+}
+/*.Q-circuit-palette,*/
+.Q-circuit-board-foreground,
+.Q-circuit-board-background {
+
+ position: relative;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.Q-circuit-clipboard {
+
+ position: absolute;
+ z-index: 100;
+ min-width: 4rem;
+ min-height: 4rem;
+ transform: scale( 1.05 );
+}
+.Q-circuit-clipboard, .Q-circuit-clipboard > div {
+
+ cursor: grabbing;
+}
+.Q-circuit-clipboard-danger .Q-circuit-operation {
+
+ background-color: var( --Q-color-yellow );
+}
+.Q-circuit-clipboard-destroy {
+
+ animation-name: Q-circuit-clipboard-poof;
+ animation-fill-mode: forwards;
+ animation-duration: 0.3s;
+ animation-iteration-count: 1;
+}
+@keyframes Q-circuit-clipboard-poof {
+
+ 100% {
+
+ transform: scale( 1.5 );
+ opacity: 0;
+ }
+}
+.Q-circuit-board-background {
+
+ /*
+
+ Clipboard: 100
+ Operation: 0
+ Shadow: -10
+ Background: -20
+
+ */
+ position: absolute;
+ z-index: -20;
+ color: rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-board-background > div {
+
+/* transition:
+ background-color 0.2s,
+ color 0.2s;*/
+}
+.Q-circuit-board-background .Q-circuit-cell-highlighted {
+
+ background-color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+ /*transition: none;*/
+}
+
+
+
+
+.Q-circuit-register-wire {
+
+ position: absolute;
+ top: calc( 50% - 0.5px );
+ width: 100%;
+ height: 1px;
+ background-color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 50%
+ );
+}
+
+
+
+.Q-circuit-palette > div,
+.Q-circuit-clipboard > div,
+.Q-circuit-board-foreground > div {
+
+ text-align: center;
+}
+
+
+
+
+
+
+ /***************/
+ /* */
+ /* Headers */
+ /* */
+/***************/
+
+
+.Q-circuit-header {
+
+ position: sticky;
+ z-index: 2;
+ margin: 0;
+ /*background-color: var( --Q-color-background );*/
+ background-color: white;
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 75%
+ );
+ font-family: var( --Q-font-family-mono );
+}
+.Q-circuit-input.Q-circuit-cell-highlighted,
+.Q-circuit-header.Q-circuit-cell-highlighted {
+
+ background-color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+ color: black;
+}
+.Q-circuit-selectall {
+
+ z-index: 3;
+ margin: 0;
+ top: 0;
+ /*left: 4rem;*/
+ /*grid-column: 2;*/
+ left: 0;
+ grid-column-start: 1;
+ grid-column-end: 3;
+ grid-row: 1;
+ cursor: se-resize;
+}
+.Q-circuit-moment-label,
+.Q-circuit-moment-add {
+
+ grid-row: 1;
+ top: 0;
+ cursor: s-resize;
+}
+.Q-circuit-register-label,
+.Q-circuit-register-add {
+
+ grid-column: 2;
+ left: 4rem;
+ cursor: e-resize;
+}
+.Q-circuit-moment-add,
+.Q-circuit-register-add {
+
+ cursor: pointer;
+}
+.Q-circuit-moment-add,
+.Q-circuit-register-add {
+
+ display: none;
+}
+.Q-circuit-selectall,
+.Q-circuit-moment-label,
+.Q-circuit-moment-add {
+
+ border-bottom: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+}
+.Q-circuit-selectall,
+.Q-circuit-register-label,
+.Q-circuit-register-add {
+
+ border-right: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+}
+.Q-circuit-input {
+
+ position: sticky;
+ z-index: 2;
+ grid-column: 1;
+ left: 0;
+ /*background-color: var( --Q-color-background );*/
+ background-color: white;
+ font-size: 1.5rem;
+ font-weight: 900;
+ font-family: var( --Q-font-family-mono );
+}
+
+
+
+
+
+
+.Q-circuit-operation-link-container {
+
+ --Q-link-stroke: 3px;
+ --Q-link-radius: 100%;
+
+ display: block;
+ position: relative;
+ left: calc( 50% - ( var( --Q-link-stroke ) / 2 ));
+ width: 50%;
+ height: 100%;
+ overflow: hidden;
+}
+.Q-circuit-operation-link-container.Q-circuit-cell-highlighted {
+
+ background-color: transparent;
+}
+.Q-circuit-operation-link {
+
+ display: block;
+ position: absolute;
+ width: calc( var( --Q-link-stroke ) * 2 );
+ height: calc( 100% - 4rem + var( --Q-link-stroke ));
+ /*border: var( --Q-link-stroke ) solid hsl( 0, 0%, 50% );*/
+ border: var( --Q-link-stroke ) solid hsl(
+
+ var( --Q-color-background-hue ),
+ 10%,
+ 30%
+ );
+
+ /*border: var( --Q-link-stroke ) solid var( --Q-color-orange );*/
+
+ transform: translate( -50%, calc( 2rem - ( var( --Q-link-stroke ) / 2 )));
+ transform-origin: center;
+}
+.Q-circuit-operation-link.Q-circuit-operation-link-curved {
+
+ width: calc( var( --Q-link-radius ) - var( --Q-link-stroke ));
+ width: 200%;
+ border-radius: 100%;
+}
+
+
+
+
+
+
+ /******************/
+ /* */
+ /* Operations */
+ /* */
+/******************/
+
+
+.Q-circuit-operation {
+
+ position: relative;
+ /*--Q-operation-color-hue: var( --Q-color-green-hue );
+ --Q-operation-color-main: var( --Q-color-green );*/
+
+ --Q-operation-color-hue: var( --Q-color-blue-hue );
+ --Q-operation-color-main: hsl(
+
+ var( --Q-operation-color-hue ),
+ 10%,
+ 35%
+ );
+
+ --Q-operation-color-light: hsl(
+
+ var( --Q-operation-color-hue ),
+ 10%,
+ 50%
+ );
+ --Q-operation-color-dark: hsl(
+
+ var( --Q-operation-color-hue ),
+ 10%,
+ 25%
+ );
+ color: white;
+ text-shadow: -0.05rem -0.05rem 0 rgba( 0, 0, 0, 0.1 );
+ font-size: 1.5rem;
+ line-height: 2.9rem;
+ font-weight: 900;
+ cursor: grab;
+}
+.Q-circuit-locked .Q-circuit-operation {
+
+ cursor: not-allowed;
+}
+.Q-circuit-operation-tile {
+
+ position: absolute;
+ top: 0.5rem;
+ left: 0.5rem;
+ right: 0.5rem;
+ bottom: 0.5rem;
+
+ /*margin: 0.5rem;*/
+ /*padding: 0.5rem;*/
+
+ /*box-shadow: 0.1rem 0.1rem 0.2rem rgba( 0, 0, 0, 0.2 );*/
+ border-radius: 0.2rem;
+ /*
+ border-top: 0.1rem solid var( --Q-operation-color-light );
+ border-left: 0.1rem solid var( --Q-operation-color-light );
+ border-right: 0.1rem solid var( --Q-operation-color-dark );
+ border-bottom: 0.1rem solid var( --Q-operation-color-dark );
+ */
+ background:
+ var( --Q-operation-color-main )
+ /*linear-gradient(
+
+ 0.45turn,
+ rgba( 255, 255, 255, 0.1 ),
+ rgba( 0, 0, 0, 0.05 )
+ )*/;
+}
+.Q-circuit-palette .Q-circuit-operation:hover {
+
+ /*background-color: rgba( 255, 255, 255, 0.6 );*/
+ background-color: white;
+}
+.Q-circuit-palette .Q-circuit-operation-tile {
+
+ --Q-before-rotation: 12deg;
+ --Q-before-x: 1px;
+ --Q-before-y: -2px;
+
+ --Q-after-rotation: -7deg;
+ --Q-after-x: -2px;
+ --Q-after-y: 3px;
+
+ box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-palette .Q-circuit-operation-tile:before,
+.Q-circuit-palette .Q-circuit-operation-tile:after {
+
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ border-radius: 0.2rem;
+ /*background-color: hsl( 0, 0%, 60% );*/
+
+ background-color: var( --Q-operation-color-dark );
+ transform:
+ translate( var( --Q-before-x ), var( --Q-before-y ))
+ rotate( var( --Q-before-rotation ));
+ z-index: -10;
+ /*z-index: 10;*/
+ display: block;
+ box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-palette .Q-circuit-operation-tile:after {
+
+ transform:
+ translate( var( --Q-after-x ), var( --Q-after-y ))
+ rotate( var( --Q-after-rotation ));
+ box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-operation:hover .Q-circuit-operation-tile {
+
+ color: white;
+}
+
+
+
+
+.Q-circuit-operation-hadamard .Q-circuit-operation-tile {
+
+ /*--Q-operation-color-hue: var( --Q-color-red-hue );*/
+ /*--Q-operation-color-main: var( --Q-color-red );*/
+
+ /*--Q-operation-color-hue: 0;
+ --Q-operation-color-main: hsl( 0, 0%, 10% );*/
+
+
+/* background:
+ linear-gradient(
+
+ -33deg,
+ var( --Q-color-blue ) 20%,
+ #6f3c69 50%,
+ var( --Q-color-red ) 80%
+ );*/
+}
+.Q-circuit-operation-identity .Q-circuit-operation-tile,
+.Q-circuit-operation-control .Q-circuit-operation-tile,
+.Q-circuit-operation-target .Q-circuit-operation-tile {
+
+ /*--Q-operation-color-hue: var( --Q-color-orange-hue );*/
+ /*--Q-operation-color-main: var( --Q-color-orange );*/
+ border-radius: 100%;
+}
+.Q-circuit-operation-identity .Q-circuit-operation-tile,
+.Q-circuit-operation-control .Q-circuit-operation-tile {
+
+ top: calc( 50% - 0.7rem );
+ left: calc( 50% - 0.7rem );
+ width: 1.4rem;
+ height: 1.4rem;
+ overflow: hidden;
+/* --Q-operation-color-hue: 0;
+ --Q-operation-color-main: hsl( 0, 0%, 10% );*/
+}
+.Q-circuit-operation-pauli-x,
+.Q-circuit-operation-pauli-y,
+.Q-circuit-operation-pauli-z {
+
+ /*--Q-operation-color-hue: var( --Q-color-red-hue );*/
+ /*--Q-operation-color-main: var( --Q-color-red );*/
+
+/* --Q-operation-color-hue: 0;
+ --Q-operation-color-main: hsl( 0, 0%, 30% );*/
+}
+.Q-circuit-operation-swap .Q-circuit-operation-tile {
+
+ top: calc( 50% - 0.55rem );
+ left: calc( 50% - 0.55rem );
+ width: 1.2rem;
+ height: 1.2rem;
+ border-radius: 0;
+ transform-origin: center;
+ transform: rotate( 45deg );
+ font-size: 0;
+}
+
+
+
+
+
+
+ /********************/
+ /* */
+ /* Other states */
+ /* */
+/********************/
+
+
+.Q-circuit-palette > div:hover,
+.Q-circuit-board-foreground > div:hover {
+
+ outline: 2px solid var( --Q-hyperlink-internal-color );
+ outline-offset: -2px;
+}
+.Q-circuit-palette > div:hover .Q-circuit-operation-tile {
+
+ box-shadow: none;
+}
+/*.Q-circuit-palette > div:hover,*/
+.Q-circuit-board-foreground > div:hover {
+
+ background-color: white;
+ color: black;
+}
+
+
+
+
+
+
+.Q-circuit-clipboard > div,
+.Q-circuit-cell-selected {
+
+ background-color: white;
+}
+.Q-circuit-clipboard > div:before,
+.Q-circuit-cell-selected:before {
+
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ display: block;
+ z-index: -10;
+ box-shadow:
+ 0 0 1rem rgba( 0, 0, 0, 0.2 ),
+ 0.4rem 0.4rem 0.2rem rgba( 0, 0, 0, 0.2 );
+ outline: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 50%
+ );
+ /*outline-offset: -1px;*/
+}
+
+
+
+
+.Q-circuit-clipboard > div {
+
+ background-color: white;
+}
+.Q-circuit-clipboard > div:before {
+
+ /*
+
+ This was very helpful!
+ https://blog.dudak.me/2014/css-shadows-under-adjacent-elements/
+
+ */
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: -10;
+ display: block;
+ box-shadow: 0.4rem 0.4rem 0.3rem rgba( 0, 0, 0, 0.2 );
+}
+
+
+
+
+
+ /***************/
+ /* */
+ /* Buttons */
+ /* */
+/***************/
+
+
+.Q-circuit-locked .Q-circuit-toggle-lock,
+.Q-circuit-locked .Q-circuit-toggle-lock:hover {
+
+ background-color: var( --Q-color-red );
+}
+.Q-circuit-toggle-lock {
+
+ z-index: 3;
+ left: 0;
+ top: 0;
+ grid-column: 1;
+ grid-row: 1;
+ cursor: pointer;
+ font-size: 1.1rem;
+ text-shadow: none;
+ font-weight: normal;
+}
+.Q-circuit-button-undo,
+.Q-circuit-button-redo {
+
+ font-size: 1.2rem;
+ line-height: 2.6rem;
+ font-weight: normal;
+}
+
+
+
+.Q-circuit p {
+
+ padding: 1rem;
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 66%
+ );
+}
+
+
+
+/*
+
+ Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+
+*/
+@charset "utf-8";
+
+
+
+
+
+
+
+
+
+/*
+
+ Z indices:
+
+ Clipboard =100
+ Selected op 10
+ Operation 0
+ Shadow -10
+ Background -20
+
+
+
+
+
+ Circuit
+
+ Menu Moments
+ ╭───────┬───┬───┬───┬───╮
+ │ ≡ ↘ │ 1 │ 2 │ 3 │ + │ Add moment
+ ├───┬───┼───┼───┼───┼───╯
+ R │ 0 │|0⟩│ H │ C0│ X │ -
+ e ├───┼───┼───┼───┼───┤
+ g │ 1 │|0⟩│ I │ C1│ X │ -
+ s ├───┼───┴───┴───┴───┘
+ │ + │ - - - -
+ ╰───╯
+ Add
+ register
+
+
+ Circuit Palette
+
+ ╭───────────────────┬───╮
+ │ H X Y Z S T π M … │ @ │
+ ╰───────────────────┴───╯
+
+
+ Circuit clipboard
+
+ ┌───────────────┐
+ ▟│ ┌───┬───────┐ │
+ █│ │ H │ X#0.0 │ │
+ █│ ├───┼───────┤ │
+ █│ │ I │ X#0.1 │ │
+ █│ └───┴───────┘ │
+ █└───────────────┘
+ ███████████████▛
+
+
+
+ ◢◣
+ ◢■■■■◣
+◢■■■■■■■■◣
+◥■■■■■■■■◤
+ ◥■■■■◤
+ ◥◤
+
+
+ ◢■■■■■■◤
+ ◢◤ ◢◤
+◢■■■■■■◤
+
+
+ ───────────
+ ╲ ╱ ╱ ╱
+ ╳ ╱ ╱
+ ╱ ╲╱ ╱
+ ───────
+
+
+ ─────⦢
+ ╱ ╱
+⦣─────
+
+
+*/
+
+
+
+
+
+.Q-circuit,
+.Q-circuit-palette {
+
+ position: relative;
+ width: 100%;
+}
+.Q-circuit-palette {
+
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ line-height: 0;
+}
+.Q-circuit-palette > div {
+
+ display: inline-block;
+ position: relative;
+ width: 4rem;
+ height: 4rem;
+}
+
+
+.Q-circuit {
+
+ margin: 1rem 0 2rem 0;
+ /*border-top: 2px solid hsl( 0, 0%, 50% );*/
+}
+.Q-parameters-box,
+.Q-circuit-board-foreground {
+ line-height: 3.85rem;
+ width: auto;
+}
+
+
+
+
+
+
+ /***************/
+ /* */
+ /* Toolbar */
+ /* */
+/***************/
+
+
+.Q-circuit-toolbar {
+
+ position: relative;
+ display: block;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ margin-bottom: 0.5rem;
+
+ box-sizing: border-box;
+ display: grid;
+ grid-auto-columns: 3.6rem;
+ grid-auto-rows: 3.0rem;
+ grid-auto-flow: column;
+
+}
+.Q-circuit-button {
+
+ position: relative;
+ display: inline-block;
+ /*margin: 0 0.5rem 0.5rem 0;*/
+ width: 3.6rem;
+ height: 3rem;
+/* box-shadow:
+ -0.1rem -0.1rem 0 rgba( 255, 255, 255, 0.8 ),
+ 0.1rem 0.1rem 0.1rem rgba( 0, 0, 0, 0.35 );*/
+
+ border-top: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 100%
+ );
+ border-right: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 90%
+ );
+ border-bottom: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 85%
+ );
+ border-left: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 97%
+ );
+ background: var( --Q-color-background );
+/* background:
+ var( --Q-color-background )
+ linear-gradient(
+
+ 0.4turn,
+
+ rgba( 0, 0, 0, 0.02 ),
+ rgba( 255, 255, 255, 0.1 )
+ );*/
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 30%
+ );
+ text-shadow: 1px 1px 0 rgba( 255, 255, 255, 1 );
+ /*border-radius: 0.5rem;*/
+ /*border-radius: 100%;*/
+ line-height: 2.9rem;
+ text-align: center;
+ cursor: pointer;
+ overflow: hidden;
+ font-weight: 900;
+}
+.Q-circuit-toolbar .Q-circuit-button:first-child {
+
+ border-top-left-radius: 0.5rem;
+ border-bottom-left-radius: 0.5rem;
+}
+.Q-circuit-toolbar .Q-circuit-button:last-child {
+
+ border-top-right-radius: 0.5rem;
+ border-bottom-right-radius: 0.5rem;
+}
+.Q-circuit-locked .Q-circuit-button,
+.Q-circuit-button[Q-disabled] {
+
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 85%
+ );
+ cursor: not-allowed;
+}
+.Q-circuit-locked .Q-circuit-toggle-lock {
+
+ color: inherit;
+ cursor: pointer;
+}
+
+
+
+
+.Q-circuit-board-container {
+
+ position: relative;
+ margin: 0 0 2rem 0;
+ margin: 0;
+ width: 100%;
+ max-height: 60vh;
+ overflow: scroll;
+}
+.Q-circuit-board {
+
+ position: relative;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+/*.Q-circuit-palette,*/
+.Q-circuit-board-foreground,
+.Q-circuit-board-background,
+.Q-circuit-clipboard {
+
+ box-sizing: border-box;
+ display: grid;
+ grid-auto-rows: 4rem;
+ grid-auto-columns: 4rem;
+ grid-auto-flow: column;
+}
+
+.Q-parameters-box {
+
+ position: absolute;
+ display: none;
+ z-index: 100;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: whitesmoke;
+}
+
+/*.Q-circuit-palette,*/
+.Q-circuit-board-foreground,
+.Q-circuit-board-background {
+
+ position: relative;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.Q-circuit-clipboard {
+
+ position: absolute;
+ z-index: 100;
+ min-width: 4rem;
+ min-height: 4rem;
+ transform: scale( 1.05 );
+}
+.Q-circuit-clipboard, .Q-circuit-clipboard > div {
+
+ cursor: grabbing;
+}
+.Q-circuit-clipboard-danger .Q-circuit-operation {
+
+ background-color: var( --Q-color-yellow );
+}
+.Q-circuit-clipboard-destroy {
+
+ animation-name: Q-circuit-clipboard-poof;
+ animation-fill-mode: forwards;
+ animation-duration: 0.3s;
+ animation-iteration-count: 1;
+}
+@keyframes Q-circuit-clipboard-poof {
+
+ 100% {
+
+ transform: scale( 1.5 );
+ opacity: 0;
+ }
+}
+.Q-circuit-board-background {
+
+ /*
+
+ Clipboard: 100
+ Operation: 0
+ Shadow: -10
+ Background: -20
+
+ */
+ position: absolute;
+ z-index: -20;
+ color: rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-board-background > div {
+
+/* transition:
+ background-color 0.2s,
+ color 0.2s;*/
+}
+.Q-circuit-board-background .Q-circuit-cell-highlighted {
+
+ background-color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+ /*transition: none;*/
+}
+
+
+
+
+.Q-circuit-register-wire {
+
+ position: absolute;
+ top: calc( 50% - 0.5px );
+ width: 100%;
+ height: 1px;
+ background-color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 50%
+ );
+}
+
+.Q-parameter-box-exit {
+ position: relative;
+ right: 0;
+ left: 0;
+ width: 5rem;
+ height: 2.5rem;
+ background-color: whitesmoke;
+}
+
+.Q-parameters-box > div,
+.Q-circuit-palette > div,
+.Q-circuit-clipboard > div,
+.Q-circuit-board-foreground > div {
+
+ text-align: center;
+}
+
+
+
+
+
+
+ /***************/
+ /* */
+ /* Headers */
+ /* */
+/***************/
+
+
+.Q-circuit-header {
+
+ position: sticky;
+ z-index: 2;
+ margin: 0;
+ /*background-color: var( --Q-color-background );*/
+ background-color: white;
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 75%
+ );
+ font-family: var( --Q-font-family-mono );
+}
+.Q-circuit-input.Q-circuit-cell-highlighted,
+.Q-circuit-header.Q-circuit-cell-highlighted {
+
+ background-color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+ color: black;
+}
+.Q-circuit-selectall {
+
+ z-index: 3;
+ margin: 0;
+ top: 0;
+ /*left: 4rem;*/
+ /*grid-column: 2;*/
+ left: 0;
+ grid-column-start: 1;
+ grid-column-end: 3;
+ grid-row: 1;
+ cursor: se-resize;
+}
+.Q-circuit-moment-label,
+.Q-circuit-moment-add {
+
+ grid-row: 1;
+ top: 0;
+ cursor: s-resize;
+}
+.Q-circuit-register-label,
+.Q-circuit-register-add {
+
+ grid-column: 2;
+ left: 4rem;
+ cursor: e-resize;
+}
+.Q-circuit-moment-add,
+.Q-circuit-register-add {
+
+ cursor: pointer;
+}
+.Q-circuit-moment-add,
+.Q-circuit-register-add {
+
+ display: none;
+}
+.Q-circuit-selectall,
+.Q-circuit-moment-label,
+.Q-circuit-moment-add {
+
+ border-bottom: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+}
+.Q-circuit-selectall,
+.Q-circuit-register-label,
+.Q-circuit-register-add {
+
+ border-right: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+}
+.Q-circuit-input {
+
+ position: sticky;
+ z-index: 2;
+ grid-column: 1;
+ left: 0;
+ /*background-color: var( --Q-color-background );*/
+ background-color: white;
+ font-size: 1.5rem;
+ font-weight: 900;
+ font-family: var( --Q-font-family-mono );
+}
+
+
+
+
+
+
+.Q-circuit-operation-link-container {
+
+ --Q-link-stroke: 3px;
+ --Q-link-radius: 100%;
+
+ display: block;
+ position: relative;
+ left: calc( 50% - ( var( --Q-link-stroke ) / 2 ));
+ width: 50%;
+ height: 100%;
+ overflow: hidden;
+}
+.Q-circuit-operation-link-container.Q-circuit-cell-highlighted {
+
+ background-color: transparent;
+}
+.Q-circuit-operation-link {
+
+ display: block;
+ position: absolute;
+ width: calc( var( --Q-link-stroke ) * 2 );
+ height: calc( 100% - 4rem + var( --Q-link-stroke ));
+ /*border: var( --Q-link-stroke ) solid hsl( 0, 0%, 50% );*/
+ border: var( --Q-link-stroke ) solid hsl(
+
+ var( --Q-color-background-hue ),
+ 10%,
+ 30%
+ );
+
+ /*border: var( --Q-link-stroke ) solid var( --Q-color-orange );*/
+
+ transform: translate( -50%, calc( 2rem - ( var( --Q-link-stroke ) / 2 )));
+ transform-origin: center;
+}
+.Q-circuit-operation-link.Q-circuit-operation-link-curved {
+
+ width: calc( var( --Q-link-radius ) - var( --Q-link-stroke ));
+ width: 200%;
+ border-radius: 100%;
+}
+
+
+
+
+
+
+ /******************/
+ /* */
+ /* Operations */
+ /* */
+/******************/
+
+.Q-circuit-operation {
+
+ position: relative;
+ /*--Q-operation-color-hue: var( --Q-color-green-hue );
+ --Q-operation-color-main: var( --Q-color-green );*/
+
+ --Q-operation-color-hue: var( --Q-color-blue-hue );
+ --Q-operation-color-main: hsl(
+
+ var( --Q-operation-color-hue ),
+ 10%,
+ 35%
+ );
+
+ --Q-operation-color-light: hsl(
+
+ var( --Q-operation-color-hue ),
+ 10%,
+ 50%
+ );
+ --Q-operation-color-dark: hsl(
+
+ var( --Q-operation-color-hue ),
+ 10%,
+ 25%
+ );
+ color: white;
+ text-shadow: -0.05rem -0.05rem 0 rgba( 0, 0, 0, 0.1 );
+ font-size: 1.5rem;
+ line-height: 2.9rem;
+ font-weight: 900;
+ cursor: grab;
+}
+.Q-circuit-locked .Q-circuit-operation {
+
+ cursor: not-allowed;
+}
+.Q-circuit-operation-tile {
+
+ position: absolute;
+ top: 0.5rem;
+ left: 0.5rem;
+ right: 0.5rem;
+ bottom: 0.5rem;
+
+ /*margin: 0.5rem;*/
+ /*padding: 0.5rem;*/
+
+ /*box-shadow: 0.1rem 0.1rem 0.2rem rgba( 0, 0, 0, 0.2 );*/
+ border-radius: 0.2rem;
+ /*
+ border-top: 0.1rem solid var( --Q-operation-color-light );
+ border-left: 0.1rem solid var( --Q-operation-color-light );
+ border-right: 0.1rem solid var( --Q-operation-color-dark );
+ border-bottom: 0.1rem solid var( --Q-operation-color-dark );
+ */
+ background:
+ var( --Q-operation-color-main )
+ /*linear-gradient(
+
+ 0.45turn,
+ rgba( 255, 255, 255, 0.1 ),
+ rgba( 0, 0, 0, 0.05 )
+ )*/;
+}
+.Q-parameter-box-exit .Q-circuit-palette .Q-circuit-operation:hover {
+
+ /*background-color: rgba( 255, 255, 255, 0.6 );*/
+ background-color: white;
+}
+.Q-circuit-palette .Q-circuit-operation-tile {
+
+ --Q-before-rotation: 12deg;
+ --Q-before-x: 1px;
+ --Q-before-y: -2px;
+
+ --Q-after-rotation: -7deg;
+ --Q-after-x: -2px;
+ --Q-after-y: 3px;
+
+ box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-palette .Q-circuit-operation-tile:before,
+.Q-circuit-palette .Q-circuit-operation-tile:after {
+
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ border-radius: 0.2rem;
+ /*background-color: hsl( 0, 0%, 60% );*/
+
+ background-color: var( --Q-operation-color-dark );
+ transform:
+ translate( var( --Q-before-x ), var( --Q-before-y ))
+ rotate( var( --Q-before-rotation ));
+ z-index: -10;
+ /*z-index: 10;*/
+ display: block;
+ box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-palette .Q-circuit-operation-tile:after {
+
+ transform:
+ translate( var( --Q-after-x ), var( --Q-after-y ))
+ rotate( var( --Q-after-rotation ));
+ box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-operation:hover .Q-circuit-operation-tile {
+
+ color: white;
+}
+
+
+
+
+.Q-circuit-operation-hadamard .Q-circuit-operation-tile {
+
+ /*--Q-operation-color-hue: var( --Q-color-red-hue );*/
+ /*--Q-operation-color-main: var( --Q-color-red );*/
+
+ /*--Q-operation-color-hue: 0;
+ --Q-operation-color-main: hsl( 0, 0%, 10% );*/
+
+
+/* background:
+ linear-gradient(
+
+ -33deg,
+ var( --Q-color-blue ) 20%,
+ #6f3c69 50%,
+ var( --Q-color-red ) 80%
+ );*/
+}
+.Q-circuit-operation-identity .Q-circuit-operation-tile,
+.Q-circuit-operation-control .Q-circuit-operation-tile,
+.Q-circuit-operation-target .Q-circuit-operation-tile {
+
+ /*--Q-operation-color-hue: var( --Q-color-orange-hue );*/
+ /*--Q-operation-color-main: var( --Q-color-orange );*/
+ border-radius: 100%;
+}
+.Q-circuit-operation-identity .Q-circuit-operation-tile,
+.Q-circuit-operation-control .Q-circuit-operation-tile {
+
+ top: calc( 50% - 0.7rem );
+ left: calc( 50% - 0.7rem );
+ width: 1.4rem;
+ height: 1.4rem;
+ overflow: hidden;
+/* --Q-operation-color-hue: 0;
+ --Q-operation-color-main: hsl( 0, 0%, 10% );*/
+}
+.Q-circuit-operation-pauli-x,
+.Q-circuit-operation-pauli-y,
+.Q-circuit-operation-pauli-z {
+
+ /*--Q-operation-color-hue: var( --Q-color-red-hue );*/
+ /*--Q-operation-color-main: var( --Q-color-red );*/
+
+/* --Q-operation-color-hue: 0;
+ --Q-operation-color-main: hsl( 0, 0%, 30% );*/
+}
+.Q-circuit-operation-swap .Q-circuit-operation-tile {
+
+ top: calc( 50% - 0.55rem );
+ left: calc( 50% - 0.55rem );
+ width: 1.2rem;
+ height: 1.2rem;
+ border-radius: 0;
+ transform-origin: center;
+ transform: rotate( 45deg );
+ font-size: 0;
+}
+
+.Q-parameter-box-input-container {
+ position: relative;
+ text-align: center;
+ grid-auto-columns: 4rem;
+ grid-auto-flow: column;
+}
+
+.Q-parameter-box-input {
+ position: relative;
+ border-radius: .2rem;
+ margin-left: 10px;
+ font-family: var( --Q-font-family-mono );
+}
+
+.Q-parameter-input-label {
+ position: relative;
+ color: var( --Q-color-blue );
+ font-family: var( --Q-font-family-mono );
+}
+
+
+
+
+ /********************/
+ /* */
+ /* Other states */
+ /* */
+/********************/
+
+
+.Q-circuit-palette > div:hover,
+.Q-circuit-board-foreground > div:hover {
+
+ outline: 2px solid var( --Q-hyperlink-internal-color );
+ outline-offset: -2px;
+}
+.Q-circuit-palette > div:hover .Q-circuit-operation-tile {
+
+ box-shadow: none;
+}
+/*.Q-circuit-palette > div:hover,*/
+.Q-circuit-board-foreground > div:hover {
+
+ background-color: white;
+ color: black;
+}
+
+
+
+
+
+
+.Q-circuit-clipboard > div,
+.Q-circuit-cell-selected {
+
+ background-color: white;
+}
+.Q-circuit-clipboard > div:before,
+.Q-circuit-cell-selected:before {
+
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ display: block;
+ z-index: -10;
+ box-shadow:
+ 0 0 1rem rgba( 0, 0, 0, 0.2 ),
+ 0.4rem 0.4rem 0.2rem rgba( 0, 0, 0, 0.2 );
+ outline: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 50%
+ );
+ /*outline-offset: -1px;*/
+}
+
+
+
+
+.Q-circuit-clipboard > div {
+
+ background-color: white;
+}
+.Q-circuit-clipboard > div:before {
+
+ /*
+
+ This was very helpful!
+ https://blog.dudak.me/2014/css-shadows-under-adjacent-elements/
+
+ */
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: -10;
+ display: block;
+ box-shadow: 0.4rem 0.4rem 0.3rem rgba( 0, 0, 0, 0.2 );
+}
+
+
+
+
+
+
+
+ /***************/
+ /* */
+ /* Buttons */
+ /* */
+/***************/
+
+
+.Q-circuit-locked .Q-circuit-toggle-lock,
+.Q-circuit-locked .Q-circuit-toggle-lock:hover {
+
+ background-color: var( --Q-color-red );
+}
+.Q-circuit-toggle-lock {
+
+ z-index: 3;
+ left: 0;
+ top: 0;
+ grid-column: 1;
+ grid-row: 1;
+ cursor: pointer;
+ font-size: 1.1rem;
+ text-shadow: none;
+ font-weight: normal;
+}
+.Q-circuit-button-undo,
+.Q-circuit-button-redo {
+
+ font-size: 1.2rem;
+ line-height: 2.6rem;
+ font-weight: normal;
+}
+
+
+
+.Q-circuit p {
+
+ padding: 1rem;
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 66%
+ );
+}
+
+
+
diff --git a/build/q-old.js b/build/bundle.js
similarity index 53%
rename from build/q-old.js
rename to build/bundle.js
index ddcf90e..07d46b7 100644
--- a/build/q-old.js
+++ b/build/bundle.js
@@ -1,2569 +1,3623 @@
+(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 1) {
+ for (var i = 1; i < arguments.length; i++) {
+ args[i - 1] = arguments[i];
+ }
+ }
+ queue.push(new Item(fun, args));
+ if (queue.length === 1 && !draining) {
+ runTimeout(drainQueue);
+ }
+};
+
+// v8 likes predictible objects
+function Item(fun, array) {
+ this.fun = fun;
+ this.array = array;
+}
+Item.prototype.run = function () {
+ this.fun.apply(null, this.array);
+};
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+process.version = ''; // empty string to avoid regexp issues
+process.versions = {};
+
+function noop() {}
+
+process.on = noop;
+process.addListener = noop;
+process.once = noop;
+process.off = noop;
+process.removeListener = noop;
+process.removeAllListeners = noop;
+process.emit = noop;
+process.prependListener = noop;
+process.prependOnceListener = noop;
+
+process.listeners = function (name) { return [] }
+
+process.binding = function (name) {
+ throw new Error('process.binding is not supported');
+};
+
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+ throw new Error('process.chdir is not supported');
+};
+process.umask = function() { return 0; };
+
+},{}],3:[function(require,module,exports){
+//Logging functions
+
+function log(verbosity = 0.5, verbosityThreshold, ...remainingArguments) {
+ if (verbosity >= verbosityThreshold) console.log(...remainingArguments);
+ return "(log)";
+}
- if( Q.verbosity >= verbosityThreshold ) console.log( ...remainingArguments )
- return '(log)'
- },
- warn: function(){
+function error() {
+ console.error(...arguments);
+ return "(error)";
+}
- console.warn( ...arguments )
- return '(warn)'
- },
- error: function(){
+function warn() {
+ console.warn(...arguments);
+ return "(error)";
+}
- console.error( ...arguments )
- return '(error)'
- },
- extractDocumentation: function( f ){
+function extractDocumentation(f) {
+ `
+ I wanted a way to document code
+ that was cleaner, more legible, and more elegant
+ than the bullshit we put up with today.
+ Also wanted it to print nicely in the console.
+ `;
+
+ f = f.toString();
+
+ const begin = f.indexOf("`") + 1,
+ end = f.indexOf("`", begin),
+ lines = f.substring(begin, end).split("\n");
+
+ function countPrefixTabs(text) {
+ // Is counting tabs “manually”
+ // actually more performant than regex?
+
+ let count = (index = 0);
+ while (text.charAt(index++) === "\t") count++;
+ return count;
+ }
+
+ //------------------- TO DO!
+ // we should check that there is ONLY whitespace between the function opening and the tick mark!
+ // otherwise it’s not documentation.
+
+ let tabs = Number.MAX_SAFE_INTEGER;
+
+ lines.forEach(function (line) {
+ if (line) {
+ const lineTabs = countPrefixTabs(line);
+ if (tabs > lineTabs) tabs = lineTabs;
+ }
+ });
+ lines.forEach(function (line, i) {
+ if (line.trim() === "") line = "\n\n";
+ lines[i] = line.substring(tabs).replace(/ {2}$/, "\n");
+ });
+ return lines.join("");
+}
- `
- I wanted a way to document code
- that was cleaner, more legible, and more elegant
- than the bullshit we put up with today.
- Also wanted it to print nicely in the console.
- `
+function help(f) {
+ if (f === undefined) f = Q;
+ return extractDocumentation(f);
+}
- f = f.toString()
-
- const
- begin = f.indexOf( '`' ) + 1,
- end = f.indexOf( '`', begin ),
- lines = f.substring( begin, end ).split( '\n' )
+function toTitleCase(text) {
+ text = text.replace(/_/g, " ");
+ return text
+ .toLowerCase()
+ .split(" ")
+ .map(function (word) {
+ return word.replace(word[0], word[0].toUpperCase());
+ })
+ .join(" ");
+}
+function centerText(text, length, filler) {
+ if (length > text.length) {
+ if (typeof filler !== "string") filler = " ";
- function countPrefixTabs( text ){
-
+ const padLengthLeft = Math.floor((length - text.length) / 2),
+ padLengthRight = length - text.length - padLengthLeft;
- // Is counting tabs “manuallyâ€
- // actually more performant than regex?
+ return text
+ .padStart(padLengthLeft + text.length, filler)
+ .padEnd(length, filler);
+ } else return text;
+}
- let count = index = 0
- while( text.charAt( index ++ ) === '\t' ) count ++
- return count
- }
+module.exports = { log, error, help, warn, toTitleCase, centerText };
+
+},{}],4:[function(require,module,exports){
+//math functions
+function hypotenuse(x, y) {
+ let a = Math.abs(x),
+ b = Math.abs(y);
+
+ if (a < 2048 && b < 2048) {
+ return Math.sqrt(a * a + b * b);
+ }
+ if (a < b) {
+ a = b;
+ b = x / y;
+ } else b = y / x;
+ return a * Math.sqrt(1 + b * b);
+}
+function logHypotenuse(x, y) {
+ const a = Math.abs(x),
+ b = Math.abs(y);
- //------------------- TO DO!
- // we should check that there is ONLY whitespace between the function opening and the tick mark!
- // otherwise it’s not documentation.
-
- let
- tabs = Number.MAX_SAFE_INTEGER
-
- lines.forEach( function( line ){
+ if (x === 0) return Math.log(b);
+ if (y === 0) return Math.log(a);
+ if (a < 2048 && b < 2048) {
+ return Math.log(x * x + y * y) / 2;
+ }
+ return Math.log(x / Math.cos(Math.atan2(y, x)));
+}
- if( line ){
-
- const lineTabs = countPrefixTabs( line )
- if( tabs > lineTabs ) tabs = lineTabs
- }
- })
- lines.forEach( function( line, i ){
+function hyperbolicSine(n) {
+ return (Math.exp(n) - Math.exp(-n)) / 2;
+}
- if( line.trim() === '' ) line = '\n\n'
- lines[ i ] = line.substring( tabs ).replace( / {2}$/, '\n' )
- })
- return lines.join( '' )
- },
- help: function( f ){
+function hyperbolicCosine(n) {
+ return (Math.exp(n) + Math.exp(-n)) / 2;
+}
- if( f === undefined ) f = Q
- return Q.extractDocumentation( f )
- },
- constants: {},
- createConstant: function( key, value ){
+function round(n, d) {
+ if (typeof d !== "number") d = 0;
+ const f = Math.pow(10, d);
+ return Math.round(n * f) / f;
+}
- //Object.freeze( value )
- this[ key ] = value
- // Object.defineProperty( this, key, {
+function isUsefulNumber(n) {
+ return (
+ isNaN(n) === false &&
+ (typeof n === "number" || n instanceof Number) &&
+ n !== Infinity &&
+ n !== -Infinity
+ );
+}
- // value,
- // writable: false
- // })
- // Object.defineProperty( this.constants, key, {
+function isUsefulInteger(n) {
+ return isUsefulNumber(n) && Number.isInteger(n);
+}
- // value,
- // writable: false
- // })
- this.constants[ key ] = this[ key ]
- Object.freeze( this[ key ])
- },
- createConstants: function(){
- if( arguments.length % 2 !== 0 ){
+module.exports = { isUsefulNumber, isUsefulInteger, hypotenuse, logHypotenuse, hyperbolicCosine, hyperbolicSine, round };
+
+},{}],5:[function(require,module,exports){
+(function (process,global){(function (){
+const logger = require('./Logging');
+
+const constants = {};
+function dispatchCustomEventToGlobal(event_name, detail, terminate_on_error=false, silent=true) {
+ try {
+ const event = new CustomEvent(event_name, detail);
+ if(typeof window != undefined) {
+ window.dispatchEvent(event);
+ }
+ else {
+ //if window does exist, global == window is true. So maybe we can just do global.dispatchEvent instead of this wrapper?
+ global.dispatchEvent(event);
+ if(!silent) console.log(event);
+ }
+ } catch(e) {
+ //When running in node, CustomEvent and documents don't exist. We can emulate using a JSDOM package
+ if(!silent) logger.error("Could not dispatch custom event.");
+ if(terminate_on_error) process.exit();
+ }
+
+}
- return Q.error( 'Q attempted to create constants with invalid (KEY, VALUE) pairs.' )
- }
- for( let i = 0; i < arguments.length; i += 2 ){
+function createConstant(key, value) {
+ //Object.freeze( value )
+ this[key] = value;
+ // Object.defineProperty( this, key, {
+
+ // value,
+ // writable: false
+ // })
+ // Object.defineProperty( this.constants, key, {
+
+ // value,
+ // writable: false
+ // })
+ constants[key] = this[key];
+ Object.freeze(this[key]);
+}
- this.createConstant( arguments[ i ], arguments[ i + 1 ])
- }
- },
+function createConstants() {
+ if (arguments.length % 2 !== 0) {
+ return logger.error(
+ "Q attempted to create constants with invalid (KEY, VALUE) pairs."
+ );
+ }
+ for (let i = 0; i < arguments.length; i += 2) {
+ createConstant(arguments[i], arguments[i + 1]);
+ }
+}
+// function loop() {}
+
+let namesIndex = 0;
+let shuffledNames = [];
+function shuffleNames$() {
+ let m = [];
+ for (let c = 0; c < COLORS.length; c++) {
+ for (let a = 0; a < ANIMALS.length; a++) {
+ m.push([c, a, Math.random()]);
+ }
+ }
+ shuffledNames = m.sort(function (a, b) {
+ return a[2] - b[2];
+ });
+}
+function getRandomName$() {
+ if (shuffledNames.length === 0) shuffleNames$();
+ const pair = shuffledNames[namesIndex],
+ name = COLORS[pair[0]] + " " + ANIMALS[pair[1]];
+ namesIndex = (namesIndex + 1) % shuffledNames.length;
+ return name;
+}
+function hueToColorName(hue) {
+ hue = hue % 360;
+ hue = Math.floor(hue / 10);
+ return COLORS[hue];
+}
- isUsefulNumber: function( n ){
+function colorIndexToHue(i) {
+ return i * 10;
+}
- return isNaN( n ) === false &&
- ( typeof n === 'number' || n instanceof Number ) &&
- n !== Infinity &&
- n !== -Infinity
- },
- isUsefulInteger: function( n ){
+createConstants(
+ "REVISION",
+ 19,
+
+ // Yeah... F’ing floating point numbers, Man!
+ // Here’s the issue:
+ // var a = new Q.ComplexNumber( 1, 2 )
+ // a.multiply(a).isEqualTo( a.power( new Q.ComplexNumber( 2, 0 )))
+ // That’s only true if Q.EPSILON >= Number.EPSILON * 6
+
+ "EPSILON",
+ Number.EPSILON * 6,
+
+ "RADIANS_TO_DEGREES",
+ 180 / Math.PI,
+
+
+ "ANIMALS",
+ [
+ "Aardvark",
+ "Albatross",
+ "Alligator",
+ "Alpaca",
+ "Ant",
+ "Anteater",
+ "Antelope",
+ "Ape",
+ "Armadillo",
+ "Baboon",
+ "Badger",
+ "Barracuda",
+ "Bat",
+ "Bear",
+ "Beaver",
+ "Bee",
+ "Bison",
+ "Boar",
+ "Buffalo",
+ "Butterfly",
+ "Camel",
+ "Caribou",
+ "Cat",
+ "Caterpillar",
+ "Cattle",
+ "Chamois",
+ "Cheetah",
+ "Chicken",
+ "Chimpanzee",
+ "Chinchilla",
+ "Chough",
+ "Clam",
+ "Cobra",
+ "Cod",
+ "Cormorant",
+ "Coyote",
+ "Crab",
+ "Crane",
+ "Crocodile",
+ "Crow",
+ "Curlew",
+ "Deer",
+ "Dinosaur",
+ "Dog",
+ "Dogfish",
+ "Dolphin",
+ "Donkey",
+ "Dotterel",
+ "Dove",
+ "Dragonfly",
+ "Duck",
+ "Dugong",
+ "Dunlin",
+ "Eagle",
+ "Echidna",
+ "Eel",
+ "Eland",
+ "Elephant",
+ "Elephant seal",
+ "Elk",
+ "Emu",
+ "Falcon",
+ "Ferret",
+ "Finch",
+ "Fish",
+ "Flamingo",
+ "Fly",
+ "Fox",
+ "Frog",
+ "Galago",
+ "Gaur",
+ "Gazelle",
+ "Gerbil",
+ "Giant Panda",
+ "Giraffe",
+ "Gnat",
+ "Gnu",
+ "Goat",
+ "Goose",
+ "Goldfinch",
+ "Goldfish",
+ "Gorilla",
+ "Goshawk",
+ "Grasshopper",
+ "Grouse",
+ "Guanaco",
+ "Guinea fowl",
+ "Guinea pig",
+ "Gull",
+ "Guppy",
+ "Hamster",
+ "Hare",
+ "Hawk",
+ "Hedgehog",
+ "Hen",
+ "Heron",
+ "Herring",
+ "Hippopotamus",
+ "Hornet",
+ "Horse",
+ "Human",
+ "Hummingbird",
+ "Hyena",
+ "Ide",
+ "Jackal",
+ "Jaguar",
+ "Jay",
+ "Jellyfish",
+ "Kangaroo",
+ "Koala",
+ "Koi",
+ "Komodo dragon",
+ "Kouprey",
+ "Kudu",
+ "Lapwing",
+ "Lark",
+ "Lemur",
+ "Leopard",
+ "Lion",
+ "Llama",
+ "Lobster",
+ "Locust",
+ "Loris",
+ "Louse",
+ "Lyrebird",
+ "Magpie",
+ "Mallard",
+ "Manatee",
+ "Marten",
+ "Meerkat",
+ "Mink",
+ "Mole",
+ "Monkey",
+ "Moose",
+ "Mouse",
+ "Mosquito",
+ "Mule",
+ "Narwhal",
+ "Newt",
+ "Nightingale",
+ "Octopus",
+ "Okapi",
+ "Opossum",
+ "Oryx",
+ "Ostrich",
+ "Otter",
+ "Owl",
+ "Ox",
+ "Oyster",
+ "Panther",
+ "Parrot",
+ "Partridge",
+ "Peafowl",
+ "Pelican",
+ "Penguin",
+ "Pheasant",
+ "Pig",
+ "Pigeon",
+ "Pony",
+ "Porcupine",
+ "Porpoise",
+ "Prairie Dog",
+ "Quail",
+ "Quelea",
+ "Rabbit",
+ "Raccoon",
+ "Rail",
+ "Ram",
+ "Raven",
+ "Reindeer",
+ "Rhinoceros",
+ "Rook",
+ "Ruff",
+ "Salamander",
+ "Salmon",
+ "Sand Dollar",
+ "Sandpiper",
+ "Sardine",
+ "Scorpion",
+ "Sea lion",
+ "Sea Urchin",
+ "Seahorse",
+ "Seal",
+ "Shark",
+ "Sheep",
+ "Shrew",
+ "Shrimp",
+ "Skunk",
+ "Snail",
+ "Snake",
+ "Sow",
+ "Spider",
+ "Squid",
+ "Squirrel",
+ "Starling",
+ "Stingray",
+ "Stinkbug",
+ "Stork",
+ "Swallow",
+ "Swan",
+ "Tapir",
+ "Tarsier",
+ "Termite",
+ "Tiger",
+ "Toad",
+ "Trout",
+ "Tui",
+ "Turkey",
+ "Turtle",
+ // U
+ "Vicuña",
+ "Viper",
+ "Vulture",
+ "Wallaby",
+ "Walrus",
+ "Wasp",
+ "Water buffalo",
+ "Weasel",
+ "Whale",
+ "Wolf",
+ "Wolverine",
+ "Wombat",
+ "Woodcock",
+ "Woodpecker",
+ "Worm",
+ "Wren",
+ // X
+ "Yak",
+ "Zebra",
+ ],
+ "ANIMALS3",
+ [
+ "ape",
+ "bee",
+ "cat",
+ "dog",
+ "elk",
+ "fox",
+ "gup",
+ "hen",
+ "ide",
+ "jay",
+ "koi",
+ "leo",
+ "moo",
+ "nit",
+ "owl",
+ "pig",
+ // Q ?
+ "ram",
+ "sow",
+ "tui",
+ // U ?
+ // V ?
+ // W ?
+ // X ?
+ "yak",
+ "zeb",
+ ],
+ "COLORS",
+ [
+ "Red", // 0 RED
+ "Scarlet", // 10
+ "Tawny", // 20
+ "Carrot", // 30
+ "Pumpkin", // 40
+ "Mustard", // 50
+ "Lemon", // 60 Yellow
+ "Lime", // 70
+ "Spring bud", // 80
+ "Spring grass", // 90
+ "Pear", // 100
+ "Kelly", // 110
+ "Green", // 120 GREEN
+ "Malachite", // 130
+ "Sea green", // 140
+ "Sea foam", // 150
+ "Aquamarine", // 160
+ "Turquoise", // 170
+ "Cyan", // 180 Cyan
+ "Pacific blue", // 190
+ "Baby blue", // 200
+ "Ocean blue", // 210
+ "Sapphire", // 220
+ "Azure", // 230
+ "Blue", // 240 BLUE
+ "Cobalt", // 250
+ "Indigo", // 260
+ "Violet", // 270
+ "Lavender", // 280
+ "Purple", // 290
+ "Magenta", // 300 Magenta
+ "Hot pink", // 310
+ "Fuschia", // 320
+ "Ruby", // 330
+ "Crimson", // 340
+ "Carmine", // 350
+ ]
+);
+
+module.exports = { createConstant, createConstants, getRandomName$, hueToColorName, colorIndexToHue, dispatchCustomEventToGlobal, constants };
+
+}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./Logging":3,"_process":2}],6:[function(require,module,exports){
- return Q.isUsefulNumber( n ) && Number.isInteger( n )
- },
- loop: function(){},
+// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+//
+const logger = require('./Logging');
+const misc = require('./Misc');
+const mathf = require('./Math-Functions');
+const {ComplexNumber} = require('./Q-ComplexNumber');
+const {Gate} = require('./Q-Gate');
+const {Qubit} = require('./Q-Qubit');
+const {Matrix} = require('./Q-Matrix');
+const {History} = require('./Q-History');
- hypotenuse: function( x, y ){
-
- let
- a = Math.abs( x ),
- b = Math.abs( y )
- if( a < 2048 && b < 2048 ){
-
- return Math.sqrt( a * a + b * b )
- }
- if( a < b ){
-
- a = b
- b = x / y
- }
- else b = y / x
- return a * Math.sqrt( 1 + b * b )
- },
- logHypotenuse: function( x, y ){
- const
- a = Math.abs( x ),
- b = Math.abs( y )
+Circuit = function( bandwidth, timewidth ){
- if( x === 0 ) return Math.log( b )
- if( y === 0 ) return Math.log( a )
- if( a < 2048 && b < 2048 ){
-
- return Math.log( x * x + y * y ) / 2
- }
- return Math.log( x / Math.cos( Math.atan2( y, x )))
- },
- hyperbolicSine: function( n ){
+ // What number Circuit is this
+ // that we’re attempting to make here?
+
+ this.index = Circuit.index ++
- return ( Math.exp( n ) - Math.exp( -n )) / 2
- },
- hyperbolicCosine: function( n ){
- return ( Math.exp( n ) + Math.exp( -n )) / 2
- },
- round: function( n, d ){
+ // How many qubits (registers) shall we use?
- if( typeof d !== 'number' ) d = 0
- const f = Math.pow( 10, d )
- return Math.round( n * f ) / f
- },
- toTitleCase: function( text ){
+ if( !mathf.isUsefulInteger( bandwidth )) bandwidth = 3
+ this.bandwidth = bandwidth
- text = text.replace( /_/g, ' ' )
- return text.toLowerCase().split( ' ' ).map( function( word ){
-
- return word.replace( word[ 0 ], word[ 0 ].toUpperCase() )
-
- }).join(' ')
- },
- centerText: function( text, length, filler ){
- if( length > text.length ){
-
- if( typeof filler !== 'string' ) filler = ' '
+ // How many operations can we perform on each qubit?
+ // Each operation counts as one moment; one clock tick.
- const
- padLengthLeft = Math.floor(( length - text.length ) / 2 ),
- padLengthRight = length - text.length - padLengthLeft
+ if( !mathf.isUsefulInteger( timewidth )) timewidth = 5
+ this.timewidth = timewidth
- return text
- .padStart( padLengthLeft + text.length, filler )
- .padEnd( length, filler )
- }
- else return text
- },
+ // We’ll start with Horizontal qubits (zeros) as inputs
+ // but we can of course modify this after initialization.
+ this.qubits = new Array( bandwidth ).fill( Qubit.HORIZONTAL )
+ // What operations will we perform on our qubits?
+
+ this.operations = []
+ // Does our circuit need evaluation?
+ // Certainly, yes!
+ // (And will again each time it is modified.)
+ this.needsEvaluation = true
+
- namesIndex: 0,
- shuffledNames: [],
- shuffleNames$: function(){
+ // When our circuit is evaluated
+ // we store those results in this array.
- let m = []
- for( let c = 0; c < Q.COLORS.length; c ++ ){
+ this.results = []
+ this.matrix = null
- for( let a = 0; a < Q.ANIMALS.length; a ++ ){
- m.push([ c, a, Math.random() ])
- }
- }
- Q.shuffledNames = m.sort( function( a, b ){
+ // Undo / Redo history.
+ this.history = new History( this )
- return a[ 2 ] - b[ 2 ]
- })
- },
- getRandomName$: function(){
+}
- if( Q.shuffledNames.length === 0 ) Q.shuffleNames$()
-
- const
- pair = Q.shuffledNames[ Q.namesIndex ],
- name = Q.COLORS[ pair[ 0 ]] +' '+ Q.ANIMALS[ pair[ 1 ]]
-
- Q.namesIndex = ( Q.namesIndex + 1 ) % Q.shuffledNames.length
- return name
- },
- hueToColorName: function( hue ){
- hue = hue % 360
- hue = Math.floor( hue / 10 )
- return Q.COLORS[ hue ]
- },
- colorIndexToHue: function( i ){
- return i * 10
- }
+Object.assign( Circuit, {
+ index: 0,
+ help: function(){ return logger.help( this )},
+ constants: {},
+ createConstant: misc.createConstant,
+ createConstants: misc.createConstants,
+ fromText: function( text ){
-})
+ // This is a quick way to enable `fromText()`
+ // to return a default new Circuit().
+ if( text === undefined ) return new Circuit()
+ // Is this a String Template -- as opposed to a regular String?
+ // If so, let’s convert it to a regular String.
+ // Yes, this maintains the line breaks.
-Q.createConstants(
+ if( text.raw !== undefined ) text = ''+text.raw
+ return Circuit.fromTableTransposed(
- 'REVISION', 19,
+ text
+ .trim()
+ .split( /\r?\n/ )
+ .filter( function( item ){ return item.length })
+ .map( function( item, r ){
+ return item
+ .trim()
+ .split( /[-+\s+=+]/ )
+ .filter( function( item ){ return item.length })
+ .map( function( item, m ){
- // Yeah... F’ing floating point numbers, Man!
- // Here’s the issue:
- // var a = new Q.ComplexNumber( 1, 2 )
- // a.multiply(a).isEqualTo( a.power( new Q.ComplexNumber( 2, 0 )))
- // That’s only true if Q.EPSILON >= Number.EPSILON * 6
-
- 'EPSILON', Number.EPSILON * 6,
-
- 'RADIANS_TO_DEGREES', 180 / Math.PI,
-
- 'ANIMALS', [
-
- 'Aardvark',
- 'Albatross',
- 'Alligator',
- 'Alpaca',
- 'Ant',
- 'Anteater',
- 'Antelope',
- 'Ape',
- 'Armadillo',
- 'Baboon',
- 'Badger',
- 'Barracuda',
- 'Bat',
- 'Bear',
- 'Beaver',
- 'Bee',
- 'Bison',
- 'Boar',
- 'Buffalo',
- 'Butterfly',
- 'Camel',
- 'Caribou',
- 'Cat',
- 'Caterpillar',
- 'Cattle',
- 'Chamois',
- 'Cheetah',
- 'Chicken',
- 'Chimpanzee',
- 'Chinchilla',
- 'Chough',
- 'Clam',
- 'Cobra',
- 'Cod',
- 'Cormorant',
- 'Coyote',
- 'Crab',
- 'Crane',
- 'Crocodile',
- 'Crow',
- 'Curlew',
- 'Deer',
- 'Dinosaur',
- 'Dog',
- 'Dogfish',
- 'Dolphin',
- 'Donkey',
- 'Dotterel',
- 'Dove',
- 'Dragonfly',
- 'Duck',
- 'Dugong',
- 'Dunlin',
- 'Eagle',
- 'Echidna',
- 'Eel',
- 'Eland',
- 'Elephant',
- 'Elephant seal',
- 'Elk',
- 'Emu',
- 'Falcon',
- 'Ferret',
- 'Finch',
- 'Fish',
- 'Flamingo',
- 'Fly',
- 'Fox',
- 'Frog',
- 'Galago',
- 'Gaur',
- 'Gazelle',
- 'Gerbil',
- 'Giant Panda',
- 'Giraffe',
- 'Gnat',
- 'Gnu',
- 'Goat',
- 'Goose',
- 'Goldfinch',
- 'Goldfish',
- 'Gorilla',
- 'Goshawk',
- 'Grasshopper',
- 'Grouse',
- 'Guanaco',
- 'Guinea fowl',
- 'Guinea pig',
- 'Gull',
- 'Guppy',
- 'Hamster',
- 'Hare',
- 'Hawk',
- 'Hedgehog',
- 'Hen',
- 'Heron',
- 'Herring',
- 'Hippopotamus',
- 'Hornet',
- 'Horse',
- 'Human',
- 'Hummingbird',
- 'Hyena',
- 'Ide',
- 'Jackal',
- 'Jaguar',
- 'Jay',
- 'Jellyfish',
- 'Kangaroo',
- 'Koala',
- 'Koi',
- 'Komodo dragon',
- 'Kouprey',
- 'Kudu',
- 'Lapwing',
- 'Lark',
- 'Lemur',
- 'Leopard',
- 'Lion',
- 'Llama',
- 'Lobster',
- 'Locust',
- 'Loris',
- 'Louse',
- 'Lyrebird',
- 'Magpie',
- 'Mallard',
- 'Manatee',
- 'Marten',
- 'Meerkat',
- 'Mink',
- 'Mole',
- 'Monkey',
- 'Moose',
- 'Mouse',
- 'Mosquito',
- 'Mule',
- 'Narwhal',
- 'Newt',
- 'Nightingale',
- 'Octopus',
- 'Okapi',
- 'Opossum',
- 'Oryx',
- 'Ostrich',
- 'Otter',
- 'Owl',
- 'Ox',
- 'Oyster',
- 'Panther',
- 'Parrot',
- 'Partridge',
- 'Peafowl',
- 'Pelican',
- 'Penguin',
- 'Pheasant',
- 'Pig',
- 'Pigeon',
- 'Pony',
- 'Porcupine',
- 'Porpoise',
- 'Prairie Dog',
- 'Quail',
- 'Quelea',
- 'Rabbit',
- 'Raccoon',
- 'Rail',
- 'Ram',
- 'Raven',
- 'Reindeer',
- 'Rhinoceros',
- 'Rook',
- 'Ruff',
- 'Salamander',
- 'Salmon',
- 'Sand Dollar',
- 'Sandpiper',
- 'Sardine',
- 'Scorpion',
- 'Sea lion',
- 'Sea Urchin',
- 'Seahorse',
- 'Seal',
- 'Shark',
- 'Sheep',
- 'Shrew',
- 'Shrimp',
- 'Skunk',
- 'Snail',
- 'Snake',
- 'Sow',
- 'Spider',
- 'Squid',
- 'Squirrel',
- 'Starling',
- 'Stingray',
- 'Stinkbug',
- 'Stork',
- 'Swallow',
- 'Swan',
- 'Tapir',
- 'Tarsier',
- 'Termite',
- 'Tiger',
- 'Toad',
- 'Trout',
- 'Tui',
- 'Turkey',
- 'Turtle',
- // U
- 'Vicuña',
- 'Viper',
- 'Vulture',
- 'Wallaby',
- 'Walrus',
- 'Wasp',
- 'Water buffalo',
- 'Weasel',
- 'Whale',
- 'Wolf',
- 'Wolverine',
- 'Wombat',
- 'Woodcock',
- 'Woodpecker',
- 'Worm',
- 'Wren',
- // X
- 'Yak',
- 'Zebra'
-
- ],
- 'ANIMALS3', [
-
- 'ape',
- 'bee',
- 'cat',
- 'dog',
- 'elk',
- 'fox',
- 'gup',
- 'hen',
- 'ide',
- 'jay',
- 'koi',
- 'leo',
- 'moo',
- 'nit',
- 'owl',
- 'pig',
- // Q ?
- 'ram',
- 'sow',
- 'tui',
- // U ?
- // V ?
- // W ?
- // X ?
- 'yak',
- 'zeb'
- ],
- 'COLORS', [
-
- 'Red', // 0 RED
- 'Scarlet', // 10
- 'Tawny', // 20
- 'Carrot', // 30
- 'Pumpkin', // 40
- 'Mustard', // 50
- 'Lemon', // 60 Yellow
- 'Lime', // 70
- 'Spring bud', // 80
- 'Spring grass',// 90
- 'Pear', // 100
- 'Kelly', // 110
- 'Green', // 120 GREEN
- 'Malachite', // 130
- 'Sea green', // 140
- 'Sea foam', // 150
- 'Aquamarine', // 160
- 'Turquoise', // 170
- 'Cyan', // 180 Cyan
- 'Pacific blue',// 190
- 'Baby blue', // 200
- 'Ocean blue', // 210
- 'Sapphire', // 220
- 'Azure', // 230
- 'Blue', // 240 BLUE
- 'Cobalt', // 250
- 'Indigo', // 260
- 'Violet', // 270
- 'Lavender', // 280
- 'Purple', // 290
- 'Magenta', // 300 Magenta
- 'Hot pink', // 310
- 'Fuschia', // 320
- 'Ruby', // 330
- 'Crimson', // 340
- 'Carmine' // 350
- ]
-)
+ //const matches = item.match( /(^\w+)(#(\w+))*(\.(\d+))*/ )
+ const matches = item.match( /(^\w+)(\.(\w+))*(#(\d+))*/ )
+ return {
+
+ gateSymbol: matches[ 1 ],
+ operationMomentId: matches[ 3 ],
+ mappingIndex: +matches[ 5 ]
+ }
+ })
+ })
+ )
+ },
-console.log( `
- QQQQQQ
-QQ QQ
-QQ QQ
-QQ QQ
-QQ QQ QQ
-QQ QQ
- QQQQ ${Q.REVISION}
-https://quantumjavascript.app
-` )
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// Working out a new syntax here... Patience please!
-// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+ fromText2: function( text ){
+ text = `
+ H C C
+ I C1 C1
+ I X1 S1
+ I X1 S1`
-Q.ComplexNumber = function( real, imaginary ){
- `
- The set of “real numbers†(â„) contains any number that can be expressed
- along an infinite timeline. https://en.wikipedia.org/wiki/Real_number
+ // This is a quick way to enable `fromText()`
+ // to return a default new Circuit().
- … -3 -2 -1 0 +1 +2 +3 …
- ┄───┴───┴───┴───┴───┴─┬─┴──┬┴┬──┄
- √2 𒆠π
+ if( text === undefined ) return new Circuit()
- Meanwhile, “imaginary numbers†(ð•€) consist of a real (â„) multiplier and
- the symbol ð’Š, which is the impossible solution to the equation ð’™Â² = −1.
- Note that no number when multiplied by itself can ever result in a
- negative product, but the concept of ð’Š gives us a way to reason around
- this imaginary scenario nonetheless.
- https://en.wikipedia.org/wiki/Imaginary_number
+ // Is this a String Template -- as opposed to a regular String?
+ // If so, let’s convert it to a regular String.
+ // Yes, this maintains the line breaks.
- … -3𒊠-2𒊠-1𒊠0𒊠+1𒊠+2𒊠+3𒊠…
- ┄───┴───┴───┴───┴───┴───┴───┴───┄
+ if( text.raw !== undefined ) text = ''+text.raw
- A “complex number“ (ℂ) is a number that can be expressed in the form
- ð’‚ + ð’ƒð’Š, where ð’‚ is the real component (â„) and ð’ƒð’Š is the imaginary
- component (ð•€). https://en.wikipedia.org/wiki/Complex_number
+ text
+ .trim()
+ .split( /\r?\n/ )
+ .filter( function( item ){ return item.length })
+ .map( function( item, r ){
- Operation functions on Q.ComplexNumber instances generally accept as
- arguments both sibling instances and pure Number instances, though the
- value returned is always an instance of Q.ComplexNumber.
+ return item
+ .trim()
+ .split( /[-+\s+=+]/ )
+ .filter( function( item ){ return item.length })
+ .map( function( item, m ){
- `
+ // +++++++++++++++++++++++
+ // need to map LETTER[] optional NUMBER ]
- if( real instanceof Q.ComplexNumber ){
+ const matches = item.match( /(^\w+)(\.(\w+))*(#(\d+))*/ )
- imaginary = real.imaginary
- real = real.real
- Q.warn( 'Q.ComplexNumber tried to create a new instance with an argument that is already a Q.ComplexNumber — and that’s weird!' )
- }
- else if( real === undefined ) real = 0
- if( imaginary === undefined ) imaginary = 0
- if(( Q.ComplexNumber.isNumberLike( real ) !== true && isNaN( real ) !== true ) ||
- ( Q.ComplexNumber.isNumberLike( imaginary ) !== true && isNaN( imaginary ) !== true ))
- return Q.error( 'Q.ComplexNumber attempted to create a new instance but the arguments provided were not actual numbers.' )
-
- this.real = real
- this.imaginary = imaginary
- this.index = Q.ComplexNumber.index ++
-}
+ //const matches = item.match( /(^\w+)(#(\w+))*(\.(\d+))*/ )
+ // const matches = item.match( /(^\w+)(\.(\w+))*(#(\d+))*/ )
+ // return {
+
+ // gateSymbol: matches[ 1 ],
+ // operationMomentId: matches[ 3 ],
+ // mappingIndex: +matches[ 5 ]
+ // }
+ })
+ })
+ },
-Object.assign( Q.ComplexNumber, {
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- index: 0,
- help: function(){ return Q.help( this )},
- constants: {},
- createConstant: Q.createConstant,
- createConstants: Q.createConstants,
- toText: function( rNumber, iNumber, roundToDecimal, padPositive ){
- // Should we round these numbers?
- // Our default is yes: to 3 digits.
- // Otherwise round to specified decimal.
- if( typeof roundToDecimal !== 'number' ) roundToDecimal = 3
- const factor = Math.pow( 10, roundToDecimal )
- rNumber = Math.round( rNumber * factor ) / factor
- iNumber = Math.round( iNumber * factor ) / factor
- // Convert padPositive
- // from a potential Boolean
- // to a String.
- // If we don’t receive a FALSE
- // then we’ll pad the positive numbers.
- padPositive = padPositive === false ? '' : ' '
+ fromTableTransposed: function( table ){
+ const
+ bandwidth = table.length,
+ timewidth = table.reduce( function( max, moments ){
- // We need the absolute values of each.
+ return Math.max( max, moments.length )
+
+ }, 0 ),
+ circuit = new Circuit( bandwidth, timewidth )
+
+ circuit.bandwidth = bandwidth
+ circuit.timewidth = timewidth
+ for( let r = 0; r < bandwidth; r ++ ){
- let
- rAbsolute = Math.abs( rNumber ),
- iAbsolute = Math.abs( iNumber )
+ const registerIndex = r + 1
+ for( let m = 0; m < timewidth; m ++ ){
+ const
+ momentIndex = m + 1,
+ operation = table[ r ][ m ]
+ let siblingHasBeenFound = false
+ for( let s = 0; s < r; s ++ ){
- // And an absolute value string.
+ const sibling = table[ s ][ m ]
+ if( operation.gateSymbol === sibling.gateSymbol &&
+ operation.operationMomentId === sibling.operationMomentId &&
+ mathf.isUsefulInteger( operation.mappingIndex ) &&
+ mathf.isUsefulInteger( sibling.mappingIndex ) &&
+ operation.mappingIndex !== sibling.mappingIndex ){
- let
- rText = rAbsolute.toString(),
- iText = iAbsolute.toString()
+ // We’ve found a sibling !
+ const operationsIndex = circuit.operations.findIndex( function( operation ){
- // Is this an IMAGINARY-ONLY number?
- // Don’t worry: -0 === 0.
+ return (
- if( rNumber === 0 ){
+ operation.momentIndex === momentIndex &&
+ operation.registerIndices.includes( s + 1 )
+ )
+ })
+ // console.log( 'operationsIndex?', operationsIndex )
+ circuit.operations[ operationsIndex ].registerIndices[ operation.mappingIndex ] = registerIndex
+ circuit.operations[ operationsIndex ].isControlled = operation.gateSymbol != '*'// Q.Gate.SWAP.
+ siblingHasBeenFound = true
+ }
+ }
+ if( siblingHasBeenFound === false && operation.gateSymbol !== 'I' ){
- if( iNumber === Infinity ) return padPositive +'∞i'
- if( iNumber === -Infinity ) return '-∞i'
- if( iNumber === 0 ) return padPositive +'0'
- if( iNumber === -1 ) return '-i'
- if( iNumber === 1 ) return padPositive +'i'
- if( iNumber >= 0 ) return padPositive + iText +'i'
- if( iNumber < 0 ) return '-'+ iText +'i'
- return iText +'i'// NaN
- }
-
+ const
+ gate = Gate.findBySymbol( operation.gateSymbol ),
+ registerIndices = []
- // This number contains a real component
- // and may also contain an imaginary one as well.
-
- if( rNumber === Infinity ) rText = padPositive +'∞'
- else if( rNumber === -Infinity ) rText = '-∞'
- else if( rNumber >= 0 ) rText = padPositive + rText
- else if( rNumber < 0 ) rText = '-'+ rText
-
- if( iNumber === Infinity ) return rText +' + ∞i'
- if( iNumber === -Infinity ) return rText +' - ∞i'
- if( iNumber === 0 ) return rText
- if( iNumber === -1 ) return rText +' - i'
- if( iNumber === 1 ) return rText +' + i'
- if( iNumber > 0 ) return rText +' + '+ iText +'i'
- if( iNumber < 0 ) return rText +' - '+ iText +'i'
- return rText +' + '+ iText +'i'// NaN
- },
+ if( mathf.isUsefulInteger( operation.mappingIndex )){
+
+ registerIndices[ operation.mappingIndex ] = registerIndex
+ }
+ else registerIndices[ 0 ] = registerIndex
+ circuit.operations.push({
-
-
-
- isNumberLike: function( n ){
-
- return isNaN( n ) === false && ( typeof n === 'number' || n instanceof Number )
- },
- isNaN: function( n ){
-
- return isNaN( n.real ) || isNaN( n.imaginary )
- },
- isZero: function( n ){
-
- return ( n.real === 0 || n.real === -0 ) &&
- ( n.imaginary === 0 || n.imaginary === -0 )
+ gate,
+ momentIndex,
+ registerIndices,
+ isControlled: false,
+ operationMomentId: operation.operationMomentId
+ })
+ }
+ }
+ }
+ circuit.sort$()
+ return circuit
},
- isFinite: function( n ){
- return isFinite( n.real ) && isFinite( n.imaginary )
- },
- isInfinite: function( n ){
-
- return !( this.isNaN( n ) || this.isFinite( n ))
- },
- areEqual: function( a, b ){
- return Q.ComplexNumber.operate(
- 'areEqual', a, b,
- function( a, b ){
-
- return Math.abs( a - b ) < Q.EPSILON
- },
- function( a, b ){
- return (
+ controlled: function( U ){
+
- Math.abs( a - b.real ) < Q.EPSILON &&
- Math.abs( b.imaginary ) < Q.EPSILON
- )
- },
- function( a, b ){
+ // we should really just replace this with a nice Matrix.copy({}) command!!!!
- return (
+ // console.log( 'U?', U )
- Math.abs( a.real - b ) < Q.EPSILON &&
- Math.abs( a.imaginary ) < Q.EPSILON
- )
- },
- function( a, b ){
+ const
+ size = U.getWidth(),
+ result = Matrix.createIdentity( size * 2 )
- return (
+ // console.log( 'U', U.toTsv() )
+ // console.log( 'size', size )
+ // console.log( 'result', result.toTsv() )
- Math.abs( a.real - b.real ) < Q.EPSILON &&
- Math.abs( a.imaginary - b.imaginary ) < Q.EPSILON
- )
+ for( let x = 0; x < size; x ++ ){
+
+ for( let y = 0; y < size; y ++ ){
+ const v = U.read( x, y )
+ // console.log( `value at ${x}, ${y}`, v )
+ result.write$( x + size, y + size, v )
}
- )
+ }
+ return result
+ },
+
+ //given an operation, return whether or not it is a valid control operation on the circuit-editor.
+ isControlledOperation: function( operation ) {
+ return (!operation.gate.is_multi_qubit || operation.gate.name === 'Swap') //assumption: we won't allow controlled multi-qubit operations
+ //..except swap or CNOT
+ && (operation.registerIndices.length >= 2)
+ && (operation.gate.can_be_controlled)
},
+ // Return transformation over entire nqubit register that applies U to
+ // specified qubits (in order given).
+ // Algorithm from Lee Spector's "Automatic Quantum Computer Programming"
+ // Page 21 in the 2004 PDF?
+ // http://148.206.53.84/tesiuami/S_pdfs/AUTOMATIC%20QUANTUM%20COMPUTER%20PROGRAMMING.pdf
+ expandMatrix: function( circuitBandwidth, U, qubitIndices ){
+ // console.log( 'EXPANDING THE MATRIX...' )
+ // console.log( 'this one: U', U.toTsv())
- absolute: function( n ){
-
- return Q.hypotenuse( n.real, n.imaginary )
- },
- conjugate: function( n ){
+ const _qubits = []
+ const n = Math.pow( 2, circuitBandwidth )
+
+
+ // console.log( 'qubitIndices used by this operation:', qubitIndices )
+ // console.log( 'qubits before slice', qubitIndices )
+ // qubitIndices = qubitIndices.slice( 0 )
+ // console.log( 'qubits AFTER slice', qubitIndices )
+
- return new Q.ComplexNumber( n.real, n.imaginary * -1 )
- },
- operate: function(
- name,
- a,
- b,
- numberAndNumber,
- numberAndComplex,
- complexAndNumber,
- complexAndComplex ){
-
- if( Q.ComplexNumber.isNumberLike( a )){
-
- if( Q.ComplexNumber.isNumberLike( b )) return numberAndNumber( a, b )
- else if( b instanceof Q.ComplexNumber ) return numberAndComplex( a, b )
- else return Q.error( 'Q.ComplexNumber attempted to', name, 'with the number', a, 'and something that is neither a Number or Q.ComplexNumber:', b )
+
+ for( let i = 0; i < qubitIndices.length; i ++ ){
+
+ //qubitIndices[ i ] = ( circuitBandwidth - 1 ) - qubitIndices[ i ]
+ qubitIndices[ i ] -= 1
}
- else if( a instanceof Q.ComplexNumber ){
+ // console.log( 'qubits AFTER manipulation', qubitIndices )
- if( Q.ComplexNumber.isNumberLike( b )) return complexAndNumber( a, b )
- else if( b instanceof Q.ComplexNumber ) return complexAndComplex( a, b )
- else return Q.error( 'Q.ComplexNumber attempted to', name, 'with the complex number', a, 'and something that is neither a Number or Q.ComplexNumber:', b )
+
+ qubitIndices.reverse()
+ for( let i = 0; i < circuitBandwidth; i ++ ){
+
+ if( qubitIndices.indexOf( i ) == -1 ){
+
+ _qubits.push( i )
+ }
}
- else return Q.error( 'Q.ComplexNumber attempted to', name, 'with something that is neither a Number or Q.ComplexNumber:', a )
- },
+ // console.log( 'qubitIndices vs _qubits:' )
+ // console.log( 'qubitIndices', qubitIndices )
+ // console.log( '_qubits', _qubits )
+
- sine: function( n ){
+ const result = new Matrix.createZero( n )
- const
- a = n.real,
- b = n.imaginary
-
- return new Q.ComplexNumber(
-
- Math.sin( a ) * Q.hyperbolicCosine( b ),
- Math.cos( a ) * Q.hyperbolicSine( b )
- )
- },
- cosine: function( n ){
- const
- a = n.real,
- b = n.imaginary
-
- return new Q.ComplexNumber(
+ // const X = numeric.rep([n, n], 0);
+ // const Y = numeric.rep([n, n], 0);
- Math.cos( a ) * Q.hyperbolicCosine( b ),
- -Math.sin( a ) * Q.hyperbolicSine( b )
- )
- },
- arcCosine: function( n ){
-
- const
- a = n.real,
- b = n.imaginary,
- t1 = Q.ComplexNumber.squareRoot( new Q.ComplexNumber(
- b * b - a * a + 1,
- a * b * -2
-
- )),
- t2 = Q.ComplexNumber.log( new Q.ComplexNumber(
+ let i = n
+ while( i -- ){
- t1.real - b,
- t1.imaginary + a
- ))
- return new Q.ComplexNumber( Math.PI / 2 - t2.imaginary, t2.real )
- },
- arcTangent: function( n ){
+ let j = n
+ while( j -- ){
+
+ let
+ bitsEqual = true,
+ k = _qubits.length
+
+ while( k -- ){
+
+ if(( i & ( 1 << _qubits[ k ])) != ( j & ( 1 << _qubits[ k ]))){
+
+ bitsEqual = false
+ break
+ }
+ }
+ if( bitsEqual ){
- const
- a = n.real,
- b = n.imaginary
+ // console.log( 'bits ARE equal' )
+ let
+ istar = 0,
+ jstar = 0,
+ k = qubitIndices.length
+
+ while( k -- ){
+
+ const q = qubitIndices[ k ]
+ istar |= (( i & ( 1 << q )) >> q ) << k
+ jstar |= (( j & ( 1 << q )) >> q ) << k
+ }
+ //console.log( 'U.read( istar, jstar )', U.read( istar, jstar ).toText() )
- if( a === 0 ){
+ // console.log( 'before write$', result.toTsv())
- if( b === 1 ) return new Q.ComplexNumber( 0, Infinity )
- if( b === -1 ) return new Q.ComplexNumber( 0, -Infinity )
- }
+ // console.log( 'U.read at ', istar, jstar, '=', U.read( istar, jstar ).toText())
+ result.write$( i, j, U.read( istar, jstar ))
- const
- d = a * a + ( 1 - b ) * ( 1 - b ),
- t = Q.ComplexNumber.log( new Q.ComplexNumber(
-
- ( 1 - b * b - a * a ) / d,
- a / d * -2
+ // console.log( 'after write$', result.toTsv())
+
+ // X[i][j] = U.x[ istar ][ jstar ]
+ // Y[i][j] = U.y[ istar ][ jstar ]
+ }
+ // else console.log('bits NOT equal')
+ }
+ }
+ //return new numeric.T(X, Y);
- ))
- return new Q.ComplexNumber( t.imaginary / 2, t.real / 2 )
+ // console.log( 'expanded matrix to:', result.toTsv() )
+ return result
},
+ evaluate: function( circuit ){
- power: function( a, b ){
+ // console.log( circuit.toDiagram() )
- if( Q.ComplexNumber.isNumberLike( a )) a = new Q.ComplexNumber( a )
- if( Q.ComplexNumber.isNumberLike( b )) b = new Q.ComplexNumber( b )
+ misc.dispatchCustomEventToGlobal(
+ 'Circuit.evaluate began', {
- // Anything raised to the Zero power is 1.
+ detail: { circuit }
+ }
+ );
- if( b.isZero() ) return Q.ComplexNumber.ONE
+ // Our circuit’s operations must be in the correct order
+ // before we attempt to step through them!
- // Zero raised to any power is 0.
- // Note: What happens if b.real is zero or negative?
- // What happens if b.imaginary is negative?
- // Do we really need those conditionals??
+ circuit.sort$()
- if( a.isZero() &&
- b.real > 0 &&
- b.imaginary >= 0 ){
- return Q.ComplexNumber.ZERO
- }
+ // Create a new matrix (or more precisely, a vector)
+ // that is a 1 followed by all zeros.
+ //
+ // ┌ ┐
+ // │ 1 │
+ // │ 0 │
+ // │ 0 │
+ // │ . │
+ // │ . │
+ // │ . │
+ // └ ┘
- // If our exponent is Real (has no Imaginary component)
- // then we’re really just raising to a power.
-
- if( b.imaginary === 0 ){
+ const state = new Matrix( 1, Math.pow( 2, circuit.bandwidth ))
+ state.write$( 0, 0, 1 )
- if( a.real >= 0 && a.imaginary === 0 ){
- return new Q.ComplexNumber( Math.pow( a.real, b.real ), 0 )
- }
- else if( a.real === 0 ){// If our base is Imaginary (has no Real component).
- switch(( b.real % 4 + 4 ) % 4 ){
-
- case 0:
- return new Q.ComplexNumber( Math.pow( a.imaginary, b.real ), 0 )
- case 1:
- return new Q.ComplexNumber( 0, Math.pow( a.imaginary, b.real ))
- case 2:
- return new Q.ComplexNumber( -Math.pow( a.imaginary, b.real ), 0 )
- case 3:
- return new Q.ComplexNumber( 0, -Math.pow( a.imaginary, b.real ))
- }
- }
- }
+ // Create a state matrix from this circuit’s input qubits.
+
+ // const state2 = circuit.qubits.reduce( function( state, qubit, i ){
- const
- arctangent2 = Math.atan2( a.imaginary, a.real ),
- logHypotenuse = Q.logHypotenuse( a.real, a.imaginary ),
- x = Math.exp( b.real * logHypotenuse - b.imaginary * arctangent2 ),
- y = b.imaginary * logHypotenuse + b.real * arctangent2
+ // if( i > 0 ) return state.multiplyTensor( qubit )
+ // else return state
- return new Q.ComplexNumber(
+ // }, circuit.qubits[ 0 ])
+ // console.log( 'Initial state', state2.toTsv() )
+ // console.log( 'multiplied', state2.multiplyTensor( state ).toTsv() )
- x * Math.cos( y ),
- x * Math.sin( y )
- )
- },
- squareRoot: function( a ){
- const
- result = new Q.ComplexNumber( 0, 0 ),
- absolute = Q.ComplexNumber.absolute( a )
- if( a.real >= 0 ){
- if( a.imaginary === 0 ){
-
- result.real = Math.sqrt( a.real )// and imaginary already equals 0.
- }
- else {
-
- result.real = Math.sqrt( 2 * ( absolute + a.real )) / 2
- }
- }
- else {
-
- result.real = Math.abs( a.imaginary ) / Math.sqrt( 2 * ( absolute - a.real ))
- }
- if( a.real <= 0 ){
-
- result.imaginary = Math.sqrt( 2 * ( absolute - a.real )) / 2
- }
- else {
-
- result.imaginary = Math.abs( a.imaginary ) / Math.sqrt( 2 * ( absolute + a.real ))
- }
- if( a.imaginary < 0 ) result.imaginary *= -1
- return result
- },
- log: function( a ){
- return new Q.ComplexNumber(
-
- Q.logHypotenuse( a.real, a.imaginary ),
- Math.atan2( a.imaginary, a.real )
- )
- },
- multiply: function( a, b ){
-
- return Q.ComplexNumber.operate(
+ const operationsTotal = circuit.operations.length
+ let operationsCompleted = 0
+ let matrix = circuit.operations.reduce( function( state, operation, i ){
- 'multiply', a, b,
- function( a, b ){
-
- return new Q.ComplexNumber( a * b )
- },
- function( a, b ){
- return new Q.ComplexNumber(
+ let U
+ if( operation.registerIndices.length < Infinity ){
+
+ if( operation.isControlled ){
+ //if( operation.registerIndices.length > 1 ){
- a * b.real,
- a * b.imaginary
- )
- },
- function( a, b ){
+ // operation.gate = Q.Gate.PAULI_X
+ // why the F was this hardcoded in there?? what was i thinking?!
+ // OH I KNOW !
+ // that was from back when i represented this as "C" -- its own gate
+ // rather than an X with multiple registers.
+ // so now no need for this "if" block at all.
+ // will remove in a few cycles.
+ }
+ U = operation.gate.matrix
+ }
+ else {
+
+ // This is for Quantum Fourier Transforms (QFT).
+ // Will have to come back to this at a later date!
+ }
+ // console.log( operation.gate.name, U.toTsv() )
- return new Q.ComplexNumber(
- a.real * b,
- a.imaginary * b
- )
- },
- function( a, b ){
- // FOIL Method that shit.
- // https://en.wikipedia.org/wiki/FOIL_method
- const
- firsts = a.real * b.real,
- outers = a.real * b.imaginary,
- inners = a.imaginary * b.real,
- lasts = a.imaginary * b.imaginary * -1// Because i² = -1.
+ // Yikes. May need to separate registerIndices in to controls[] and targets[] ??
+ // Works for now tho.....
+ // Houston we have a problem. Turns out, not every gate with registerIndices.length > 1 is
+ // controlled.
+ // This is a nasty fix, leads to a lot of edge cases. But just experimenting.
+ if( Circuit.isControlledOperation(operation) ) {
+ const scale = operation.registerIndices.length - ( operation.gate.is_multi_qubit ? 2 : 1)
+ for( let j = 0; j < scale; j ++ ){
- return new Q.ComplexNumber(
-
- firsts + lasts,
- outers + inners
- )
+ U = Circuit.controlled( U )
+ // console.log( 'qubitIndex #', j, 'U = Circuit.controlled( U )', U.toTsv() )
+ }
}
- )
- },
- divide: function( a, b ){
-
- return Q.ComplexNumber.operate(
- 'divide', a, b,
- function( a, b ){
-
- return new Q.ComplexNumber( a / b )
- },
- function( a, b ){
- return new Q.ComplexNumber( a ).divide( b )
- },
- function( a, b ){
+ // We need to send a COPY of the registerIndices Array
+ // to .expandMatrix()
+ // otherwise it *may* modify the actual registerIndices Array
+ // and wow -- tracking down that bug was painful!
- return new Q.ComplexNumber(
+ const registerIndices = operation.registerIndices.slice()
+ state = Circuit.expandMatrix(
- a.real / b,
- a.imaginary / b
- )
- },
- function( a, b ){
+ circuit.bandwidth,
+ U,
+ registerIndices
+ ).multiply( state )
+
- // Ermergerd I had to look this up because it’s been so long.
- // https://www.khanacademy.org/math/precalculus/imaginary-and-complex-numbers/complex-conjugates-and-dividing-complex-numbers/a/dividing-complex-numbers-review
- const
- conjugate = b.conjugate(),
- numerator = a.multiply( conjugate ),
+ operationsCompleted ++
+ const progress = operationsCompleted / operationsTotal
- // The .imaginary will be ZERO for sure,
- // so this forces a ComplexNumber.divide( Number ) ;)
-
- denominator = b.multiply( conjugate ).real
+ misc.dispatchCustomEventToGlobal('Circuit.evaluate progressed', { detail: {
- return numerator.divide( denominator )
- }
- )
- },
- add: function( a, b ){
-
- return Q.ComplexNumber.operate(
+ circuit,
+ progress,
+ operationsCompleted,
+ operationsTotal,
+ momentIndex: operation.momentIndex,
+ registerIndices: operation.registerIndices,
+ gate: operation.gate.name,
+ state
- 'add', a, b,
- function( a, b ){
+ }})
- return new Q.ComplexNumber( a + b )
- },
- function( a, b ){
- return new Q.ComplexNumber(
+ // console.log( `\n\nProgress ... ${ Math.round( operationsCompleted / operationsTotal * 100 )}%`)
+ // console.log( 'Moment .....', operation.momentIndex )
+ // console.log( 'Registers ..', JSON.stringify( operation.registerIndices ))
+ // console.log( 'Gate .......', operation.gate.name )
+ // console.log( 'Intermediate result:', state.toTsv() )
+ // console.log( '\n' )
+
- b.real + a,
- b.imaginary
- )
- },
- function( a, b ){
+ return state
+
+ }, state )
- return new Q.ComplexNumber(
- a.real + b,
- a.imaginary
- )
- },
- function( a, b ){
+
- return new Q.ComplexNumber(
- a.real + b.real,
- a.imaginary + b.imaginary
- )
- }
- )
- },
- subtract: function( a, b ){
- return Q.ComplexNumber.operate(
+ const outcomes = matrix.rows.reduce( function( outcomes, row, i ){
- 'subtract', a, b,
- function( a, b ){
+ outcomes.push({
- return new Q.ComplexNumber( a - b )
- },
- function( a, b ){
+ state: '|'+ parseInt( i, 10 ).toString( 2 ).padStart( circuit.bandwidth, '0' ) +'⟩',
+ probability: Math.pow( row[ 0 ].absolute(), 2 )
+ })
+ return outcomes
+
+ }, [] )
- return new Q.ComplexNumber(
- b.real - a,
- b.imaginary
- )
- },
- function( a, b ){
- return new Q.ComplexNumber(
+ circuit.needsEvaluation = false
+ circuit.matrix = matrix
+ circuit.results = outcomes
- a.real - b,
- a.imaginary
- )
- },
- function( a, b ){
- return new Q.ComplexNumber(
- a.real - b.real,
- a.imaginary - b.imaginary
- )
- }
- )
- }
-})
+ misc.dispatchCustomEventToGlobal('Circuit.evaluate completed', { detail: {
+ // circuit.dispatchEvent( new CustomEvent( 'evaluation complete', { detail: {
+ circuit,
+ results: outcomes
+ }})
-Q.ComplexNumber.createConstants(
- 'ZERO', new Q.ComplexNumber( 0, 0 ),
- 'ONE', new Q.ComplexNumber( 1, 0 ),
- 'E', new Q.ComplexNumber( Math.E, 0 ),
- 'PI', new Q.ComplexNumber( Math.PI, 0 ),
- 'I', new Q.ComplexNumber( 0, 1 ),
- 'EPSILON', new Q.ComplexNumber( Q.EPSILON, Q.EPSILON ),
- 'INFINITY', new Q.ComplexNumber( Infinity, Infinity ),
- 'NAN', new Q.ComplexNumber( NaN, NaN )
-)
+
+ return matrix
+ }
+})
+
-Object.assign( Q.ComplexNumber.prototype, {
- // NON-destructive operations.
+Object.assign( Circuit.prototype, {
clone: function(){
- return new Q.ComplexNumber( this.real, this.imaginary )
- },
- reduce: function(){
+ const
+ original = this,
+ clone = original.copy()
+ clone.qubits = original.qubits.slice()
+ clone.results = original.results.slice()
+ clone.needsEvaluation = original.needsEvaluation
- // Note: this *might* kill function chaining.
-
- if( this.imaginary === 0 ) return this.real
- return this
+ return clone
},
- toText: function( roundToDecimal, padPositive ){
-
-
- // Note: this will kill function chaining.
+ evaluate$: function(){
- return Q.ComplexNumber.toText( this.real, this.imaginary, roundToDecimal, padPositive )
+ Circuit.evaluate( this )
+ return this
},
+ report$: function( length ){
-
- isNaN: function( n ){
+ if( this.needsEvaluation ) this.evaluate$()
+ if( !mathf.isUsefulInteger( length )) length = 20
- return Q.ComplexNumber.isNaN( this )// Returned boolean will kill function chaining.
- },
- isZero: function( n ){
+ const
+ circuit = this,
+ text = this.results.reduce( function( text, outcome, i ){
- return Q.ComplexNumber.isZero( this )// Returned boolean will kill function chaining.
- },
- isFinite: function( n ){
+ const
+ probabilityPositive = Math.round( outcome.probability * length ),
+ probabilityNegative = length - probabilityPositive
- return Q.ComplexNumber.isFinite( this )// Returned boolean will kill function chaining.
- },
- isInfinite: function( n ){
-
- return Q.ComplexNumber.isInfinite( this )// Returned boolean will kill function chaining.
- },
- isEqualTo: function( b ){
+ return text +'\n'
+ + ( i + 1 ).toString().padStart( Math.ceil( Math.log10( Math.pow( 2, circuit.qubits.length ))), ' ' ) +' '
+ + outcome.state +' '
+ + ''.padStart( probabilityPositive, '█' )
+ + ''.padStart( probabilityNegative, '░' )
+ + mathf.round( Math.round( 100 * outcome.probability ), 8 ).toString().padStart( 4, ' ' ) +'% chance'
- return Q.ComplexNumber.areEqual( this, b )// Returned boolean will kill function chaining.
+ }, '' ) + '\n'
+ return text
},
+ try$: function(){
+ if( this.needsEvaluation ) this.evaluate$()
- absolute: function(){
-
- return Q.ComplexNumber.absolute( this )// Returned number will kill function chaining.
- },
- conjugate: function(){
-
- return Q.ComplexNumber.conjugate( this )
- },
-
+
+ // We need to “stack” our probabilities from 0..1.
+
+ const outcomesStacked = new Array( this.results.length )
+ this.results.reduce( function( sum, outcome, i ){
- power: function( b ){
+ sum += outcome.probability
+ outcomesStacked[ i ] = sum
+ return sum
+
+ }, 0 )
+
- return Q.ComplexNumber.power( this, b )
- },
- squareRoot: function(){
+ // Now we can pick a random number
+ // and return the first outcome
+ // with a probability equal to or greater than
+ // that random number.
+
+ const
+ randomNumber = Math.random(),
+ randomIndex = outcomesStacked.findIndex( function( index ){
- return Q.ComplexNumber.squareRoot( this )
- },
- log: function(){
+ return randomNumber <= index
+ })
+
- return Q.ComplexNumber.log( this )
+ // Output that to the console
+ // but return the random index
+ // so we can pipe that to something else
+ // should we want to :)
+
+ // console.log( this.outcomes[ randomIndex ].state )
+ return randomIndex
},
- multiply: function( b ){
- return Q.ComplexNumber.multiply( this, b )
- },
- divide: function( b ){
- return Q.ComplexNumber.divide( this, b )
- },
- add: function( b ){
- return Q.ComplexNumber.add( this, b )
- },
- subtract: function( b ){
- return Q.ComplexNumber.subtract( this, b )
- },
+ ////////////////
+ // //
+ // Output //
+ // //
+ ////////////////
+ // This is absolutely required by toTable.
+ sort$: function(){
- // DESTRUCTIVE operations.
- copy$: function( b ){
-
- if( b instanceof Q.ComplexNumber !== true )
- return Q.error( `Q.ComplexNumber attempted to copy something that was not a complex number in to this complex number #${this.index}.`, this )
-
- this.real = b.real
- this.imaginary = b.imaginary
- return this
- },
- conjugate$: function(){
+ // Sort this circuit’s operations
+ // primarily by momentIndex,
+ // then by the first registerIndex.
- return this.copy$( this.conjugate() )
- },
- power$: function( b ){
+ this.operations.sort( function( a, b ){
- return this.copy$( this.power( b ))
- },
- squareRoot$: function(){
+ if( a.momentIndex === b.momentIndex ){
- return this.copy$( this.squareRoot() )
- },
- log$: function(){
- return this.copy$( this.log() )
- },
- multiply$: function( b ){
+ // Note that we are NOT sorting registerIndices here!
+ // We are merely asking which set of indices contain
+ // the lowest register index.
+ // If we instead sorted the registerIndices
+ // we could confuse which qubit is the controller
+ // and which is the controlled!
- return this.copy$( this.multiply( b ))
- },
- divide$: function( b ){
+ return Math.min( ...a.registerIndices ) - Math.min( b.registerIndices )
+ }
+ else {
- return this.copy$( this.divide( b ))
+ return a.momentIndex - b.momentIndex
+ }
+ })
+ return this
},
- add$: function( b ){
+
- return this.copy$( this.add( b ))
- },
- subtract$: function( b ){
- return this.copy$( this.subtract( b ))
- }
-})
+ ///////////////////
+ // //
+ // Exporters //
+ // //
+ ///////////////////
-// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+ // Many export functions rely on toTable
+ // and toTable itself absolutely relies on
+ // a circuit’s operations to be SORTED correctly.
+ // We could force circuit.sort$() here,
+ // but then toTable would become toTable$
+ // and every exporter that relies on it would
+ // also become destructive.
+ toTable: function(){
+ const
+ table = new Array( this.timewidth ),
+ circuit = this
-Q.Matrix = function(){
+ // Sure, this is equal to table.length
+ // but isn’t legibility and convenience everything?
- // We’re keeping track of how many matrices are
- // actually being generated. Just curiosity.
+ table.timewidth = this.timewidth
+
- this.index = Q.Matrix.index ++
+ // Similarly, this should be equal to table[ 0 ].length
+ // or really table[ i >= 0; i < table.length ].length,
+ // but again, lowest cognitive hurdle is key ;)
+ table.bandwidth = this.bandwidth
+
- let matrixWidth = null
+ // First, let’s establish a “blank” table
+ // that contains an identity operation
+ // for each register during each moment.
+ table.fill( 0 ).forEach( function( element, index, array ){
- // Has Matrix been called with two numerical arguments?
- // If so, we need to create an empty Matrix
- // with dimensions of those values.
-
- if( arguments.length == 1 &&
- Q.ComplexNumber.isNumberLike( arguments[ 0 ])){
+ const operations = new Array( circuit.bandwidth )
+ operations.fill( 0 ).forEach( function( element, index, array ){
- matrixWidth = arguments[ 0 ]
- this.rows = new Array( matrixWidth ).fill( 0 ).map( function(){
+ array[ index ] = {
- return new Array( matrixWidth ).fill( 0 )
+ symbol: 'I',
+ symbolDisplay: 'I',
+ name: 'Identity',
+ nameCss: 'identity',
+ gateInputIndex: 0,
+ bandwidth: 0,
+ thisGateAmongMultiQubitGatesIndex: 0,
+ aSiblingIsAbove: false,
+ aSiblingIsBelow: false
+ }
+ })
+ array[ index ] = operations
})
- }
- else if( arguments.length == 2 &&
- Q.ComplexNumber.isNumberLike( arguments[ 0 ]) &&
- Q.ComplexNumber.isNumberLike( arguments[ 1 ])){
- matrixWidth = arguments[ 0 ]
- this.rows = new Array( arguments[ 1 ]).fill( 0 ).map( function(){
- return new Array( matrixWidth ).fill( 0 )
- })
- }
- else {
+ // Now iterate through the circuit’s operations list
+ // and note those operations in our table.
+ // NOTE: This relies on operations being pre-sorted with .sort$()
+ // prior to the .toTable() call.
+
+ let
+ momentIndex = 1,
+ multiRegisterOperationIndex = 0,
+ gateTypesUsedThisMoment = {}
- // Matrices’ primary organization is by rows,
- // which is more congruent with our written langauge;
- // primarily organizated by horizontally juxtaposed glyphs.
- // That means it’s easier to write an instance invocation in code
- // and easier to read when inspecting properties in the console.
+ this.operations.forEach( function( operation, operationIndex, operations ){
- let matrixWidthIsBroken = false
- this.rows = Array.from( arguments )
- this.rows.forEach( function( row ){
- if( row instanceof Array !== true ) row = [ row ]
- if( matrixWidth === null ) matrixWidth = row.length
- else if( matrixWidth !== row.length ) matrixWidthIsBroken = true
- })
- if( matrixWidthIsBroken )
- return Q.error( `Q.Matrix found upon initialization that matrix#${this.index} row lengths were not equal. You are going to have a bad time.`, this )
- }
+ // We need to keep track of
+ // how many multi-register operations
+ // occur during this moment.
+ if( momentIndex !== operation.momentIndex ){
+ table[ momentIndex ].gateTypesUsedThisMoment = gateTypesUsedThisMoment
+ momentIndex = operation.momentIndex
+ multiRegisterOperationIndex = 0
+ gateTypesUsedThisMoment = {}
+ }
+ if( operation.registerIndices.length > 1 ){
+ table[ momentIndex - 1 ].multiRegisterOperationIndex = multiRegisterOperationIndex
+ multiRegisterOperationIndex ++
+ }
+ if( gateTypesUsedThisMoment[ operation.gate.symbol ] === undefined ){
+ gateTypesUsedThisMoment[ operation.gate.symbol ] = 1
+ }
+ else gateTypesUsedThisMoment[ operation.gate.symbol ] ++
- // But for convenience we can also organize by columns.
- // Note this represents the transposed version of itself!
+ // By default, an operation’s CSS name
+ // is its regular name, all lowercase,
+ // with all spaces replaced by hyphens.
- const matrix = this
- this.columns = []
- for( let x = 0; x < matrixWidth; x ++ ){
-
- const column = []
- for( let y = 0; y < this.rows.length; y ++ ){
-
-
- // Since we’re combing through here
- // this is a good time to convert Number to ComplexNumber!
-
- const value = matrix.rows[ y ][ x ]
- if( typeof value === 'number' ){
-
- // console.log('Created a complex number!')
- matrix.rows[ y ][ x ] = new Q.ComplexNumber( value )
- }
- else if( value instanceof Q.ComplexNumber === false ){
- return Q.error( `Q.Matrix found upon initialization that matrix#${this.index} contained non-quantitative values. A+ for creativity, but F for functionality.`, this )
- }
+ let nameCss = operation.gate.name.toLowerCase().replace( /\s+/g, '-' )
- // console.log( x, y, matrix.rows[ y ][ x ])
+ operation.registerIndices.forEach( function( registerIndex, indexAmongSiblings ){
- Object.defineProperty( column, y, {
-
- get: function(){ return matrix.rows[ y ][ x ]},
- set: function( n ){ matrix.rows[ y ][ x ] = n }
- })
- }
- this.columns.push( column )
- }
-}
-
-
-
+ let isMultiRegisterOperation = false
+ if( operation.registerIndices.length > 1 ){
+ isMultiRegisterOperation = true
+ if( indexAmongSiblings === operation.registerIndices.length - 1 ){
+ nameCss = 'target'
+ }
+ else {
- ///////////////////////////
- // //
- // Static properties //
- // //
-///////////////////////////
+ nameCss = 'control'
+ }
+ // May need to re-visit the code above in consideration of SWAPs.
-Object.assign( Q.Matrix, {
+ }
+ table[ operation.momentIndex - 1 ][ registerIndex - 1 ] = {
- index: 0,
- help: function(){ return Q.help( this )},
- constants: {},// Only holds references; an easy way to look up what constants exist.
- createConstant: Q.createConstant,
- createConstants: Q.createConstants,
+ symbol: operation.gate.symbol,
+ symbolDisplay: operation.gate.symbol,
+ name: operation.gate.name,
+ nameCss,
+ operationIndex,
+ momentIndex: operation.momentIndex,
+ registerIndex,
+ isMultiRegisterOperation,
+ multiRegisterOperationIndex,
+ gatesOfThisTypeNow: gateTypesUsedThisMoment[ operation.gate.symbol ],
+ indexAmongSiblings,
+ siblingExistsAbove: Math.min( ...operation.registerIndices ) < registerIndex,
+ siblingExistsBelow: Math.max( ...operation.registerIndices ) > registerIndex
+ }
+ })
+/*
- isMatrixLike: function( obj ){
- //return obj instanceof Q.Matrix || Q.Matrix.prototype.isPrototypeOf( obj )
- return obj instanceof this || this.prototype.isPrototypeOf( obj )
- },
- isWithinRange: function( n, minimum, maximum ){
+++++++++++++++++++++++
- return typeof n === 'number' &&
- n >= minimum &&
- n <= maximum &&
- n == parseInt( n )
- },
- getWidth: function( matrix ){
+Non-fatal problem to solve here:
- return matrix.columns.length
- },
- getHeight: function( matrix ){
+Previously we were concerned with “gates of this type used this moment”
+when we were thinking about CNOT as its own special gate.
+But now that we treat CNOT as just connected X gates,
+we now have situations
+where a moment can have one “CNOT” but also a stand-alone X gate
+and toTable will symbol the “CNOT” as X.0
+(never X.1, because it’s the only multi-register gate that moment)
+but still uses the symbol X.0 instead of just X
+because there’s another stand-alone X there tripping the logic!!!
- return matrix.rows.length
- },
- haveEqualDimensions: function( matrix0, matrix1 ){
- return (
-
- matrix0.rows.length === matrix1.rows.length &&
- matrix0.columns.length === matrix1.columns.length
- )
- },
- areEqual: function( matrix0, matrix1 ){
- if( matrix0 instanceof Q.Matrix !== true ) return false
- if( matrix1 instanceof Q.Matrix !== true ) return false
- if( Q.Matrix.haveEqualDimensions( matrix0, matrix1 ) !== true ) return false
- return matrix0.rows.reduce( function( state, row, r ){
- return state && row.reduce( function( state, cellValue, c ){
- return state && cellValue.isEqualTo( matrix1.rows[ r ][ c ])
+*/
- }, true )
- }, true )
- },
+ // if( operationIndex === operations.length - 1 ){
+
+ table[ momentIndex - 1 ].gateTypesUsedThisMoment = gateTypesUsedThisMoment
+ // }
+ })
- createSquare: function( size, f ){
- if( typeof size !== 'number' ) size = 2
- if( typeof f !== 'function' ) f = function(){ return 0 }
- const data = []
- for( let y = 0; y < size; y ++ ){
- const row = []
- for( let x = 0; x < size; x ++ ){
- row.push( f( x, y ))
- }
- data.push( row )
- }
- return new Q.Matrix( ...data )
- },
- createZero: function( size ){
-
- return new Q.Matrix.createSquare( size )
- },
- createOne: function( size ){
-
- return new Q.Matrix.createSquare( size, function(){ return 1 })
- },
- createIdentity: function( size ){
- return new Q.Matrix.createSquare( size, function( x, y ){ return x === y ? 1 : 0 })
- },
-
- // Import FROM a format.
+ table.forEach( function( moment, m ){
- from: function( format ){
+ moment.forEach( function( operation, o ){
- if( typeof format !== 'string' ) format = 'Array'
- const f = Q.Matrix[ 'from'+ format ]
- format = format.toLowerCase()
- if( typeof f !== 'function' )
- return Q.error( `Q.Matrix could not find an importer for “${format}†data.` )
- return f
- },
- fromArray: function( array ){
+ if( operation.isMultiRegisterOperation ){
- return new Q.Matrix( ...array )
- },
- fromXsv: function( input, rowSeparator, valueSeparator ){
+ if( moment.gateTypesUsedThisMoment[ operation.symbol ] > 1 ){
- `
- Ingest string data organized by row, then by column
- where rows are separated by one token (default: \n)
- and column values are separated by another token
- (default: \t).
+ operation.symbolDisplay = operation.symbol +'.'+ ( operation.gatesOfThisTypeNow - 1 )
+ }
+ operation.symbolDisplay += '#'+ operation.indexAmongSiblings
+ }
+ })
+ })
- `
- if( typeof rowSeparator !== 'string' ) rowSeparator = '\n'
- if( typeof valueSeparator !== 'string' ) valueSeparator = '\t'
+ // Now we can easily read down each moment
+ // and establish the moment’s character width.
+ // Very useful for text-based diagrams ;)
- const
- inputRows = input.split( rowSeparator ),
- outputRows = []
+ table.forEach( function( moment ){
- inputRows.forEach( function( inputRow ){
+ const maximumWidth = moment.reduce( function( maximumWidth, operation ){
- inputRow = inputRow.trim()
- if( inputRow === '' ) return
+ return Math.max( maximumWidth, operation.symbolDisplay.length )
- const outputRow = []
- inputRow.split( valueSeparator ).forEach( function( cellValue ){
-
- outputRow.push( parseFloat( cellValue ))
- })
- outputRows.push( outputRow )
+ }, 1 )
+ moment.maximumCharacterWidth = maximumWidth
})
- return new Q.Matrix( ...outputRows )
- },
- fromCsv: function( csv ){
- return Q.Matrix.fromXsv( csv.replace( /\r/g, '\n' ), '\n', ',' )
- },
- fromTsv: function( tsv ){
- return Q.Matrix.fromXsv( tsv, '\n', '\t' )
- },
- fromHtml: function( html ){
+ // We can also do this for the table as a whole.
+
+ table.maximumCharacterWidth = table.reduce( function( maximumWidth, moment ){
+
+ return Math.max( maximumWidth, moment.maximumCharacterWidth )
+
+ }, 1 )
- return Q.Matrix.fromXsv(
- html
- .replace( /\r?\n|\r||/g, '' )
- .replace( /<\/td>(\s*)<\/tr>/g, ' ' )
- .match( /(.*)<\/table>/i )[ 1 ],
- '',
- ''
- )
- },
+ // I think we’re done here.
+ return table
+ },
+ toText: function( makeAllMomentsEqualWidth ){
+ `
+ Create a text representation of this circuit
+ using only common characters,
+ ie. no fancy box-drawing characters.
+ This is the complement of Circuit.fromText()
+ `
+ const
+ table = this.toTable(),
+ output = new Array( table.bandwidth ).fill( '' )
- // Export TO a format.
+ for( let x = 0; x < table.timewidth; x ++ ){
- toXsv: function( matrix, rowSeparator, valueSeparator ){
-
- return matrix.rows.reduce( function( xsv, row ){
+ for( let y = 0; y < table.bandwidth; y ++ ){
- return xsv + rowSeparator + row.reduce( function( xsv, cell, c ){
+ let cellString = table[ x ][ y ].symbolDisplay.padEnd( table[ x ].maximumCharacterWidth, '-' )
+ if( makeAllMomentsEqualWidth && x < table.timewidth - 1 ){
- return xsv + ( c > 0 ? valueSeparator : '' ) + cell.toText()
-
- }, '' )
-
- }, '' )
+ cellString = table[ x ][ y ].symbolDisplay.padEnd( table.maximumCharacterWidth, '-' )
+ }
+ if( x > 0 ) cellString = '-'+ cellString
+ output[ y ] += cellString
+ }
+ }
+ return '\n'+ output.join( '\n' )
+ // return output.join( '\n' )
},
- toCsv: function( matrix ){
+ toDiagram: function( makeAllMomentsEqualWidth ){
- return Q.Matrix.toXsv( matrix, '\n', ',' )
- },
- toTsv: function( matrix ){
+ `
+ Create a text representation of this circuit
+ using fancy box-drawing characters.
+ `
- return Q.Matrix.toXsv( matrix, '\n', '\t' )
- },
+ const
+ scope = this,
+ table = this.toTable(),
+ output = new Array( table.bandwidth * 3 + 1 ).fill( '' )
+ output[ 0 ] = ' '
+ scope.qubits.forEach( function( qubit, q ){
+ const y3 = q * 3
+ output[ y3 + 1 ] += ' '
+ output[ y3 + 2 ] += 'r'+ ( q + 1 ) +' |'+ qubit.beta.toText().trim() +'⟩─'
+ output[ y3 + 3 ] += ' '
+ })
+ for( let x = 0; x < table.timewidth; x ++ ){
+ const padToLength = makeAllMomentsEqualWidth
+ ? table.maximumCharacterWidth
+ : table[ x ].maximumCharacterWidth
- // Operate NON-destructive.
+ output[ 0 ] += logger.centerText( 'm'+ ( x + 1 ), padToLength + 4 )
+ for( let y = 0; y < table.bandwidth; y ++ ){
- add: function( matrix0, matrix1 ){
+ let
+ operation = table[ x ][ y ],
+ first = '',
+ second = '',
+ third = ''
- if( Q.Matrix.isMatrixLike( matrix0 ) !== true ||
- Q.Matrix.isMatrixLike( matrix1 ) !== true ){
+ if( operation.symbol === 'I' ){
- return Q.error( `Q.Matrix attempted to add something that was not a matrix.` )
- }
- if( Q.Matrix.haveEqualDimensions( matrix0, matrix1 ) !== true )
- return Q.error( `Q.Matrix cannot add matrix#${matrix0.index} of dimensions ${matrix0.columns.length}x${matrix0.rows.length} to matrix#${matrix1.index} of dimensions ${matrix1.columns.length}x${matrix1.rows.length}.`)
+ first += ' '
+ second += '──'
+ third += ' '
+
+ first += ' '.padEnd( padToLength )
+ second += logger.centerText( '○', padToLength, '─' )
+ third += ' '.padEnd( padToLength )
- return new Q.Matrix( ...matrix0.rows.reduce( function( resultMatrixRow, row, r ){
+ first += ' '
+ if( x < table.timewidth - 1 ) second += '──'
+ else second += ' '
+ third += ' '
+ }
+ else {
- resultMatrixRow.push( row.reduce( function( resultMatrixColumn, cellValue, c ){
+ if( operation.isMultiRegisterOperation ){
- // resultMatrixColumn.push( cellValue + matrix1.rows[ r ][ c ])
- resultMatrixColumn.push( cellValue.add( matrix1.rows[ r ][ c ]))
- return resultMatrixColumn
+ first += '╭─'
+ third += '╰─'
+ }
+ else {
+
+ first += '┌─'
+ third += '└─'
+ }
+ second += '┤ '
+
+ first += '─'.padEnd( padToLength, '─' )
+ second += logger.centerText( operation.symbolDisplay, padToLength )
+ third += '─'.padEnd( padToLength, '─' )
- }, [] ))
- return resultMatrixRow
- }, [] ))
- },
- multiplyScalar: function( matrix, scalar ){
+ if( operation.isMultiRegisterOperation ){
- if( Q.Matrix.isMatrixLike( matrix ) !== true ){
+ first += '─╮'
+ third += '─╯'
+ }
+ else {
- return Q.error( `Q.Matrix attempted to scale something that was not a matrix.` )
- }
- if( typeof scalar !== 'number' ){
+ first += '─┐'
+ third += '─┘'
+ }
+ second += x < table.timewidth - 1 ? ' ├' : ' │'
- return Q.error( `Q.Matrix attempted to scale this matrix#${matrix.index} by an invalid scalar: ${scalar}.` )
- }
- return new Q.Matrix( ...matrix.rows.reduce( function( resultMatrixRow, row ){
+ if( operation.isMultiRegisterOperation ){
- resultMatrixRow.push( row.reduce( function( resultMatrixColumn, cellValue ){
+ let n = ( operation.multiRegisterOperationIndex * 2 ) % ( table[ x ].maximumCharacterWidth + 1 ) + 1
+ if( operation.siblingExistsAbove ){
- // resultMatrixColumn.push( cellValue * scalar )
- resultMatrixColumn.push( cellValue.multiply( scalar ))
- return resultMatrixColumn
-
- }, [] ))
- return resultMatrixRow
+ first = first.substring( 0, n ) +'┴'+ first.substring( n + 1 )
+ }
+ if( operation.siblingExistsBelow ){
- }, [] ))
+ third = third.substring( 0, n ) +'┬'+ third.substring( n + 1 )
+ }
+ }
+ }
+ const y3 = y * 3
+ output[ y3 + 1 ] += first
+ output[ y3 + 2 ] += second
+ output[ y3 + 3 ] += third
+ }
+ }
+ return '\n'+ output.join( '\n' )
},
- multiply: function( matrix0, matrix1 ){
- `
- Two matrices can be multiplied only when
- the number of columns in the first matrix
- equals the number of rows in the second matrix.
- Reminder: Matrix multiplication is not commutative
- so the order in which you multiply matters.
- SEE ALSO
- https://en.wikipedia.org/wiki/Matrix_multiplication
- `
+ // Oh yes my friends... WebGL is coming!
- if( Q.Matrix.isMatrixLike( matrix0 ) !== true ||
- Q.Matrix.isMatrixLike( matrix1 ) !== true ){
+ toShader: function(){
- return Q.error( `Q.Matrix attempted to multiply something that was not a matrix.` )
- }
- if( matrix0.columns.length !== matrix1.rows.length ){
+ },
+ toGoogleCirq: function(){
+/*
- return Q.error( `Q.Matrix attempted to multiply Matrix#${matrix0.index}(cols==${matrix0.columns.length}) by Matrix#${matrix1.index}(rows==${matrix1.rows.length}) but their dimensions were not compatible for this.` )
- }
- const resultMatrix = []
- matrix0.rows.forEach( function( matrix0Row ){// Each row of THIS matrix
- const resultMatrixRow = []
- matrix1.columns.forEach( function( matrix1Column ){// Each column of OTHER matrix
+cirq.GridQubit(4,5)
- const sum = new Q.ComplexNumber()
- matrix1Column.forEach( function( matrix1CellValue, index ){// Work down the column of OTHER matrix
+https://cirq.readthedocs.io/en/stable/tutorial.html
- sum.add$( matrix0Row[ index ].multiply( matrix1CellValue ))
- })
- resultMatrixRow.push( sum )
- })
- resultMatrix.push( resultMatrixRow )
- })
- //return new Q.Matrix( ...resultMatrix )
- return new this( ...resultMatrix )
+*/
+ const header = `import cirq`
+
+ return headers
},
- multiplyTensor: function( matrix0, matrix1 ){
+ toAmazonBraket: function(){
+ let isValidBraketCircuit = true
+ const header = `import boto3
+from braket.aws import AwsDevice
+from braket.circuits import Circuit
- `
- https://en.wikipedia.org/wiki/Kronecker_product
- https://en.wikipedia.org/wiki/Tensor_product
- `
+my_bucket = f"amazon-braket-Your-Bucket-Name" # the name of the bucket
+my_prefix = "Your-Folder-Name" # the name of the folder in the bucket
+s3_folder = (my_bucket, my_prefix)\n
+device = LocalSimulator()\n\n`
+//TODO (ltnln): Syntax is different for simulators and actual quantum computers. Should there be a default? Should there be a way to change?
+//vs an actual quantum computer? May not be necessary.
+ let variables = ''
+ let num_unitaries = 0
+ //`qjs_circuit = Circuit().h(0).cnot(0,1)`
+ //ltnln change: from gate.AmazonBraketName -> gate.symbolAmazonBraket
+ let circuit = this.operations.reduce( function( string, operation ){
+ let awsGate = operation.gate.symbolAmazonBraket
+ isValidBraketCircuit &= awsGate !== undefined
+ if( operation.gate.symbolAmazonBraket === undefined ) isValidBraketCircuit = false
+ if( operation.gate.symbol === 'X' ) {
+ if( operation.registerIndices.length === 1 ) awsGate = operation.gate.symbolAmazonBraket
+ else if( operation.registerIndices.length === 2 ) awsGate = 'cnot'
+ else if( operation.registerIndices.length === 3) awsGate = 'ccnot'
+ else isValidBraketCircuit = false
+ }
- if( Q.Matrix.isMatrixLike( matrix0 ) !== true ||
- Q.Matrix.isMatrixLike( matrix1 ) !== true ){
+ else if( operation.gate.symbol === 'S' ) {
+ if( operation.gate.parameters["phi"] === 0 ) {
+ awsGate = operation.registerIndices.length == 2 ? awsGate : "cswap"
+ return string +'.'+ awsGate +'(' +
+ operation.registerIndices.reduce( function( string, registerIndex, r ){
- return Q.error( `Q.Matrix attempted to tensor something that was not a matrix.` )
- }
+ return string + (( r > 0 ) ? ',' : '' ) + ( registerIndex - 1 )
- const
- resultMatrix = [],
- resultMatrixWidth = matrix0.columns.length * matrix1.columns.length,
- resultMatrixHeight = matrix0.rows.length * matrix1.rows.length
+ }, '' ) + ')'
+ }
+ awsGate = 'pswap'
+ }
+ //ltnln note: removed the if( operation.gate.symbol == '*') branch as it should be covered by
+ //the inclusion of the CURSOR gate.
+ else if( ['Y','Z','P'].includes( operation.gate.symbol) ) {
+ if( operation.registerIndices.length === 1) awsGate = operation.gate.symbolAmazonBraket
+ else if( operation.registerIndices.length === 2 ) awsGate = (operation.gate.symbol === 'Y') ? 'cy' : (operation.gate.symbol === 'Z') ? 'cz' : 'cphaseshift'
+ else isValidBraketCircuit = false
+ }
+ //for all unitary gates, there must be a line of code to initialize the matrix for use
+ //in Braket's .u(matrix=my_unitary, targets[0]) function
+ else if( operation.gate.symbol === 'U') {
+ //check that this truly works as a unique id
+ isValidBraketCircuit &= operation.registerIndices.length === 1
+ const new_matrix = `unitary_` + num_unitaries
+ num_unitaries++
+ //https://en.wikipedia.org/wiki/Unitary_matrix; source for the unitary matrix values implemented below.
+ const a = ComplexNumber.toText(Math.cos(-(operation.gate.parameters[ "phi" ] + operation.gate.parameters[ "lambda" ])*Math.cos(operation.gate.parameters[ "theta" ] / 2) / 2),
+ Math.sin(-(operation.gate.parameters[ "phi" ] + operation.gate.parameters[ "lambda" ])*Math.cos(operation.gate.parameters[ "theta" ] / 2) / 2))
+ .replace('i', 'j')
+ const b = ComplexNumber.toText(-Math.cos(-(operation.gate.parameters[ "phi" ] - operation.gate.parameters[ "lambda" ])*Math.sin(operation.gate.parameters[ "theta" ] / 2) / 2),
+ -Math.sin(-(operation.gate.parameters[ "phi" ] - operation.gate.parameters[ "lambda" ])*Math.sin(operation.gate.parameters[ "theta" ] / 2)) / 2)
+ .replace('i', 'j')
+ const c = ComplexNumber.toText(Math.cos((operation.gate.parameters[ "phi" ] - operation.gate.parameters[ "lambda" ])*Math.sin(operation.gate.parameters[ "theta" ] / 2) / 2),
+ -Math.sin((operation.gate.parameters[ "phi" ] - operation.gate.parameters[ "lambda" ])*Math.sin(operation.gate.parameters[ "theta" ] / 2)) / 2)
+ .replace('i', 'j')
+ const d = ComplexNumber.toText(Math.cos((operation.gate.parameters[ "phi" ] + operation.gate.parameters[ "lambda" ])*Math.cos(operation.gate.parameters[ "theta" ] / 2) / 2),
+ Math.sin((operation.gate.parameters[ "phi" ] + operation.gate.parameters[ "lambda" ])*Math.cos(operation.gate.parameters[ "theta" ] / 2)) / 2)
+ .replace('i', 'j')
+ variables += new_matrix + ` = np.array(` +
+ `[[` + a + ', ' + b + `],`+
+ `[` + c + ', ' + d + `]])\n`
+ return string +'.'+ awsGate +'(' + new_matrix +','+
+ operation.registerIndices.reduce( function( string, registerIndex, r ){
- for( let y = 0; y < resultMatrixHeight; y ++ ){
+ return string + (( r > 0 ) ? ',' : '' ) + ( registerIndex - 1 )
- const resultMatrixRow = []
- for( let x = 0; x < resultMatrixWidth; x ++ ){
+ }, '' ) + ')'
+ }
+ // I believe this line should ensure that we don't include any controlled single-qubit gates that aren't allowed in Braket.
+ // The registerIndices.length > 1 technically shouldn't be necessary, but if changes are made later, it's just for safety.
+ else isValidBraketCircuit &= (operation.registerIndices.length === 1) || ( operation.registerIndices.length > 1 && operation.gate.is_multi_qubit )
+ return string +'.'+ awsGate +'(' +
+ operation.registerIndices.reduce( function( string, registerIndex, r ){
- const
- matrix0X = Math.floor( x / matrix0.columns.length ),
- matrix0Y = Math.floor( y / matrix0.rows.length ),
- matrix1X = x % matrix1.columns.length,
- matrix1Y = y % matrix1.rows.length
+ return string + (( r > 0 ) ? ',' : '' ) + ( registerIndex - 1 )
- resultMatrixRow.push(
+ }, '' ) + ((operation.gate.has_parameters) ?
+ Object.values( operation.gate.parameters ).reduce( function( string, parameter ) {
+ return string + "," + parameter
+ }, '')
+ : '') + ')'
- //matrix0.rows[ matrix0Y ][ matrix0X ] * matrix1.rows[ matrix1Y ][ matrix1X ]
- matrix0.rows[ matrix0Y ][ matrix0X ].multiply( matrix1.rows[ matrix1Y ][ matrix1X ])
- )
- }
- resultMatrix.push( resultMatrixRow )
- }
- return new Q.Matrix( ...resultMatrix )
- }
-})
+ }, 'qjs_circuit = Circuit()' )
+ variables += '\n'
+ if( this.operations.length === 0 ) circuit += '.i(0)'// Quick fix to avoid an error here!
+ const footer = `\n\ntask = device.run(qjs_circuit, s3_folder, shots=100)
+print(task.result().measurement_counts)`
+ return isValidBraketCircuit ? header + variables + circuit + footer : `###This circuit is not representable as a Braket circuit!###`
+ },
+ toLatex: function(){
+ /*
+ \Qcircuit @C=1em @R=.7em {
+ & \ctrl{2} & \targ & \gate{U} & \qw \\
+ & \qw & \ctrl{-1} & \qw & \qw \\
+ & \targ & \ctrl{-1} & \ctrl{-2} & \qw \\
+ & \qw & \ctrl{-1} & \qw & \qw
+ }
+ No "&"" means it’s an input. So could also do this:
+ \Qcircuit @C=1.4em @R=1.2em {
+ a & i \\
+ 1 & x
+ }
+ */
- //////////////////////////////
- // //
- // Prototype properties //
- // //
-//////////////////////////////
+ return '\\Qcircuit @C=1.0em @R=0.7em {\n' +
+ this.toTable()
+ .reduce( function( array, moment, m ){
+ moment.forEach( function( operation, o, operations ){
-Object.assign( Q.Matrix.prototype, {
+ let command = 'qw'
+ if( operation.symbol !== 'I' ){
- isValidRow: function( r ){
+ if( operation.isMultiRegisterOperation ){
- return Q.Matrix.isWithinRange( r, 0, this.rows.length - 1 )
- },
- isValidColumn: function( c ){
+ if( operation.indexAmongSiblings === 0 ){
- return Q.Matrix.isWithinRange( c, 0, this.columns.length - 1 )
- },
- isValidAddress: function( x, y ){
+ if( operation.symbol === 'X' ) command = 'targ'
+ else command = operation.symbol.toLowerCase()
+ }
+ else if( operation.indexAmongSiblings > 0 ) command = 'ctrl{?}'
+ }
+ else command = operation.symbol.toLowerCase()
+ }
+ operations[ o ].latexCommand = command
+ })
+ const maximumCharacterWidth = moment.reduce( function( maximumCharacterWidth, operation ){
- return this.isValidRow( y ) && this.isValidColumn( x )
- },
- getWidth: function(){
+ return Math.max( maximumCharacterWidth, operation.latexCommand.length )
+
+ }, 0 )
+ moment.forEach( function( operation, o ){
- return Q.Matrix.getWidth( this )
- },
- getHeight: function(){
+ array[ o ] += '& \\'+ operation.latexCommand.padEnd( maximumCharacterWidth ) +' '
+ })
+ return array
- return Q.Matrix.getHeight( this )
+ }, new Array( this.bandwidth ).fill( '\n\t' ))
+ .join( '\\\\' ) +
+ '\n}'
},
- // Read NON-destructive by nature. (Except quantum reads of course! ROFL!!)
-
- read: function( x, y ){
-
- `
- Equivalent to
- this.columns[ x ][ y ]
- or
- this.rows[ y ][ x ]
- but with safety checks.
- `
-
- if( this.isValidAddress( x, y )) return this.rows[ y ][ x ]
- return Q.error( `Q.Matrix could not read from cell address (x=${x}, y=${y}) in matrix#${this.index}.`, this )
- },
- clone: function(){
- return new Q.Matrix( ...this.rows )
- },
- isEqualTo: function( otherMatrix ){
- return Q.Matrix.areEqual( this, otherMatrix )
- },
+ //////////////
+ // //
+ // Edit //
+ // //
+ //////////////
- toArray: function(){
+ get: function( momentIndex, registerIndex ){
- return this.rows
- },
- toXsv: function( rowSeparator, valueSeparator ){
-
- return Q.Matrix.toXsv( this, rowSeparator, valueSeparator )
- },
- toCsv: function(){
+ return this.operations.find( function( op ){
- return Q.Matrix.toXsv( this, '\n', ',' )
+ return op.momentIndex === momentIndex &&
+ op.registerIndices.includes( registerIndex )
+ })
},
- toTsv: function(){
+ clear$: function( momentIndex, registerIndices ){
- return Q.Matrix.toXsv( this, '\n', '\t' )
- },
- toHtml: function(){
-
- return this.rows.reduce( function( html, row ){
+ const circuit = this
- return html + row.reduce( function( html, cell ){
- return html +'\n\t\t'+ cell.toText() +' '
-
- }, '\n\t' ) + '\n\t '
+ // Validate our arguments.
- }, '\n'
- },
-
-
-
+ if( arguments.length !== 2 )
+ logger.warn( `Circuit.clear$ expected 2 arguments but received ${ arguments.length }.` )
+ if( mathf.isUsefulInteger( momentIndex ) !== true )
+ return logger.error( `Circuit attempted to clear an input on Circuit #${ circuit.index } using an invalid moment index:`, momentIndex )
+ if( mathf.isUsefulInteger( registerIndices )) registerIndices = [ registerIndices ]
+ if( registerIndices instanceof Array !== true )
+ return logger.error( `Circuit attempted to clear an input on Circuit #${ circuit.index } using an invalid register indices array:`, registerIndices )
- // Write is DESTRUCTIVE by nature. Not cuz I hate ya.
- write$: function( x, y, n ){
+ // Let’s find any operations
+ // with a footprint at this moment index and one of these register indices
+ // and collect not only their content, but their index in the operations array.
+ // (We’ll need that index to splice the operations array later.)
- `
- Equivalent to
- this.columns[ x ][ y ] = n
- or
- this.rows[ y ][ x ] = n
- but with safety checks.
- `
+ const foundOperations = circuit.operations.reduce( function( filtered, operation, o ){
- if( this.isValidAddress( x, y )){
+ if( operation.momentIndex === momentIndex &&
+ operation.registerIndices.some( function( registerIndex ){
- if( Q.ComplexNumber.isNumberLike( n )) n = new Q.ComplexNumber( n )
- if( n instanceof Q.ComplexNumber !== true ) return Q.error( `Attempted to write an invalid value (${n}) to matrix#${this.index} at x=${x}, y=${y}`, this )
- this.rows[ y ][ x ] = n
- return this
- }
- return Q.error( `Invalid cell address for Matrix#${this.index}: x=${x}, y=${y}`, this )
- },
- copy$: function( matrix ){
+ return registerIndices.includes( registerIndex )
+ })
+ ) filtered.push({
- if( Q.Matrix.isMatrixLike( matrix ) !== true )
- return Q.error( `Q.Matrix attempted to copy something that was not a matrix in to this matrix#${matrix.index}.`, this )
+ index: o,
+ momentIndex: operation.momentIndex,
+ registerIndices: operation.registerIndices,
+ gate: operation.gate
+ })
+ return filtered
- if( Q.Matrix.haveEqualDimensions( matrix, this ) !== true )
- return Q.error( `Q.Matrix cannot copy matrix#${matrix.index} of dimensions ${matrix.columns.length}x${matrix.rows.length} in to this matrix#${this.index} of dimensions ${this.columns.length}x${this.rows.length} because their dimensions do not match.`, this )
-
- const that = this
- matrix.rows.forEach( function( row, r ){
+ }, [] )
- row.forEach( function( n, c ){
- that.rows[ r ][ c ] = n
- })
- })
- return this
- },
- fromArray$: function( array ){
+ // Because we held on to each found operation’s index
+ // within the circuit’s operations array
+ // we can now easily splice them out of the array.
- return this.copy$( Q.Matrix.fromArray( array ))
- },
- fromCsv$: function( csv ){
+ foundOperations.reduce( function( deletionsSoFar, operation ){
- return this.copy$( Q.Matrix.fromCsv( csv ))
- },
- fromTsv$: function( tsv ){
+ circuit.operations.splice( operation.index - deletionsSoFar, 1 )
+ return deletionsSoFar + 1
- return this.copy$( Q.Matrix.fromTsv( tsv ))
- },
- fromHtml$: function( html ){
+ }, 0 )
- return this.copy$( Q.Matrix.fromHtml( html ))
- },
+ // IMPORTANT!
+ // Operations must be sorted properly
+ // for toTable to work reliably with
+ // multi-register operations!!
+
+ this.sort$()
+ // Let’s make history.
- // Operate NON-destructive.
+ if( foundOperations.length ){
- add: function( otherMatrix ){
+ this.history.record$({
- return Q.Matrix.add( this, otherMatrix )
- },
- multiplyScalar: function( scalar ){
+ redo: {
+
+ name: 'clear$',
+ func: circuit.clear$,
+ args: Array.from( arguments )
+ },
+ undo: foundOperations.reduce( function( undos, operation ){
- return Q.Matrix.multiplyScalar( this, scalar )
- },
- multiply: function( otherMatrix ){
+ undos.push({
- return Q.Matrix.multiply( this, otherMatrix )
- },
- multiplyTensor: function( otherMatrix ){
+ name: 'set$',
+ func: circuit.set$,
+ args: [
- return Q.Matrix.multiplyTensor( this, otherMatrix )
- },
+ operation.gate,
+ operation.momentIndex,
+ operation.registerIndices
+ ]
+ })
+ return undos
+
+ }, [] )
+ })
+ // Let anyone listening,
+ // including any circuit editor interfaces,
+ // know about what we’ve just completed here.
+ foundOperations.forEach( function( operation ){
- // Operate DESTRUCTIVE.
+ misc.dispatchCustomEventToGlobal(
- add$: function( otherMatrix ){
+ 'Circuit.clear$', { detail: {
- return this.copy$( this.add( otherMatrix ))
+ circuit,
+ momentIndex,
+ registerIndices: operation.registerIndices
+ }}
+ )
+ })
+ }
+
+
+ // Enable that “fluent interface” method chaining :)
+
+ return circuit
},
- multiplyScalar$: function( scalar ){
+
- return this.copy$( this.multiplyScalar( scalar ))
- }
-})
+ setProperty$: function( key, value ){
+ this[ key ] = value
+ return this
+ },
+ setName$: function( name ){
+ if( typeof name === 'function' ) name = name()
+ return this.setProperty$( 'name', name )
+ },
+ set$: function( gate, momentIndex, registerIndices, parameters = {} ){
+ const circuit = this
- //////////////////////////
- // //
- // Static constants //
- // //
-//////////////////////////
+ // Is this a valid gate?
+ // We clone the gate rather than using the constant; this way, if we change it's parameters, we don't change the constant.
+ if( typeof gate === 'string' ) gate = Gate.prototype.clone( Gate.findBySymbol( gate ) )
+ if( gate instanceof Gate !== true ) return logger.error( `Circuit attempted to add a gate (${ gate }) to circuit #${ this.index } at moment #${ momentIndex } that is not a gate:`, gate )
-Q.Matrix.createConstants(
+ // Is this a valid moment index?
+
+ if( mathf.isUsefulNumber( momentIndex ) !== true ||
+ Number.isInteger( momentIndex ) !== true ||
+ momentIndex < 1 || momentIndex > this.timewidth ){
- 'IDENTITY_2X2', Q.Matrix.createIdentity( 2 ),
- 'IDENTITY_3X3', Q.Matrix.createIdentity( 3 ),
- 'IDENTITY_4X4', Q.Matrix.createIdentity( 4 ),
+ return logger.error( `Circuit attempted to add a gate to circuit #${ this.index } at a moment index that is not valid:`, momentIndex )
+ }
- 'CONSTANT0_2X2', new Q.Matrix(
- [ 1, 1 ],
- [ 0, 0 ]),
- 'CONSTANT1_2X2', new Q.Matrix(
- [ 0, 0 ],
- [ 1, 1 ]),
+ // Are these valid register indices?
- 'NEGATION_2X2', new Q.Matrix(
- [ 0, 1 ],
- [ 1, 0 ]),
+ if( typeof registerIndices === 'number' ) registerIndices = [ registerIndices ]
+ if( registerIndices instanceof Array !== true ) return logger.error( `Circuit attempted to add a gate to circuit #${ this.index } at moment #${ momentIndex } with an invalid register indices array:`, registerIndices )
+ if( registerIndices.length === 0 ) return logger.error( `Circuit attempted to add a gate to circuit #${ this.index } at moment #${ momentIndex } with an empty register indices array:`, registerIndices )
+ if( registerIndices.reduce( function( accumulator, registerIndex ){
- 'TEST_MAP_9X9', new Q.Matrix(
- [ 11, 21, 31, 41, 51, 61, 71, 81, 91 ],
- [ 12, 22, 32, 42, 52, 62, 72, 82, 92 ],
- [ 13, 23, 33, 43, 53, 63, 73, 83, 93 ],
- [ 14, 24, 34, 44, 54, 64, 74, 84, 94 ],
- [ 15, 25, 35, 45, 55, 65, 75, 85, 95 ],
- [ 16, 26, 36, 46, 56, 66, 76, 86, 96 ],
- [ 17, 27, 37, 47, 57, 67, 77, 87, 97 ],
- [ 18, 28, 38, 48, 58, 68, 78, 88, 98 ],
- [ 19, 29, 39, 49, 59, 69, 79, 89, 99 ])
-)
+ // console.log(accumulator &&
+ // registerIndex > 0 &&
+ // registerIndex <= circuit.bandwidth)
+ return (
+ accumulator &&
+ registerIndex > 0 &&
+ registerIndex <= circuit.bandwidth
+ )
+ }, false )){
+ return logger.warn( `Circuit attempted to add a gate to circuit #${ this.index } at moment #${ momentIndex } with some out of range qubit indices:`, registerIndices )
+ }
-// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+ // Ok, now we can check if this set$ command
+ // is redundant.
+ const
+ isRedundant = !!circuit.operations.find( function( operation ){
+ return (
-Q.Qubit = function( a, b, symbol, name ){
-
+ momentIndex === operation.momentIndex &&
+ gate === operation.gate &&
+ registerIndices.length === operation.registerIndices.length &&
+ registerIndices.every( val => operation.registerIndices.includes( val ))
+ )
+ })
- // If we’ve received an instance of Q.Matrix as our first argument
- // then we’ll assume there are no further arguments
- // and just use that matrix as our new Q.Qubit instance.
- if( Q.Matrix.isMatrixLike( a ) && b === undefined ){
+ // If it’s NOT redundant
+ // then we’re clear to proceed.
- b = a.rows[ 1 ][ 0 ]
- a = a.rows[ 0 ][ 0 ]
- }
- else {
+ if( isRedundant !== true ){
- // All of our internal math now uses complex numbers
- // rather than Number literals
- // so we’d better convert!
+ // If there’s already an operation here,
+ // we’d better get rid of it!
+ // This will also entirely remove any multi-register operations
+ // that happen to have a component at this moment / register.
+
+ this.clear$( momentIndex, registerIndices )
+
+
+ // Finally.
+ // Finally we can actually set this operation.
+ // Aren’t you glad we handle all this for you?
- if( typeof a === 'number' ) a = new Q.ComplexNumber( a, 0 )
- if( typeof b === 'number' ) b = new Q.ComplexNumber( b, 0 )
+ const
+ //TODO: For ltnln (have to fix)
+ // a) allow users to control whatever they want! Just because it's not allowed in Braket
+ // doesn't mean they shouldn't be allowed to do it in Q! (Probably fixable by adjusting toAmazonBraket)
+ // b) Controlling a multi_qubit gate will not treat the control icon like a control gate!
+ isControlled = registerIndices.length > 1 && gate !== Gate.SWAP && gate.can_be_controlled !== undefined
+ operation = {
+ gate,
+ momentIndex,
+ registerIndices,
+ isControlled
+ }
+ //perform parameter update here!!!
+ if(gate.has_parameters) gate.updateMatrix$.apply( gate, Object.values(parameters) )
+ this.operations.push( operation )
- // If we receive undefined (or garbage inputs)
- // let’s try to make it useable.
- // This way we can always call Q.Qubit with no arguments
- // to make a new qubit available for computing with.
+
+ // IMPORTANT!
+ // Operations must be sorted properly
+ // for toTable to work reliably with
+ // multi-register operations!!
+
+ this.sort$()
- if( a instanceof Q.ComplexNumber !== true ) a = new Q.ComplexNumber( 1, 0 )
- if( b instanceof Q.ComplexNumber !== true ){
+ // Let’s make history.
+ const redo_args = Array.from( arguments )
+ Object.assign( redo_args[ redo_args.length - 1 ], parameters )
+ this.history.record$({
- // 1 - |ð’‚|² = |ð’ƒ|²
- // So this does NOT account for if ð’ƒ ought to be imaginary or not.
- // Perhaps for completeness we could randomly decide
- // to flip the real and imaginary components of ð’ƒ after this line?
+ redo: {
+
+ name: 'set$',
+ func: circuit.set$,
+ args: redo_args
+ },
+ undo: [{
- b = Q.ComplexNumber.ONE.subtract( Math.pow( a.absolute(), 2 )).squareRoot()
- }
- }
+ name: 'clear$',
+ func: circuit.clear$,
+ args: [ momentIndex, registerIndices ]
+ }]
+ })
+
+ // Emit an event that we have set an operation
+ // on this circuit.
- // Sanity check!
- // Does this constraint hold true? |ð’‚|² + |ð’ƒ|² = 1
+ misc.dispatchCustomEventToGlobal(
- if( Math.pow( a.absolute(), 2 ) + Math.pow( b.absolute(), 2 ) - 1 > Q.EPSILON )
- return Q.error( `Q.Qubit could not accept the initialization values of a=${a} and b=${b} because their squares do not add up to 1.` )
+ 'Circuit.set$', { detail: {
- Q.Matrix.call( this, [ a ],[ b ])
- this.index = Q.Qubit.index ++
+ circuit,
+ operation
+ }}
+ )
+ }
+ return circuit
+ },
- // Convenience getters and setters for this qubit’s
- // controll bit and target bit.
- Object.defineProperty( this, 'alpha', {
- get: function(){ return this.rows[ 0 ][ 0 ]},
- set: function( n ){ this.rows[ 0 ][ 0 ] = n }
- })
- Object.defineProperty( this, 'beta', {
+ determineRanges: function( options ){
- get: function(){ return this.rows[ 1 ][ 0 ]},
- set: function( n ){ this.rows[ 1 ][ 0 ] = n }
- })
+ if( options === undefined ) options = {}
+ let {
+ qubitFirstIndex,
+ qubitRange,
+ qubitLastIndex,
+ momentFirstIndex,
+ momentRange,
+ momentLastIndex
- // Used for Dirac notation: |?⟩
+ } = options
- if( typeof symbol === 'string' ) this.symbol = symbol
- if( typeof name === 'string' ) this.name = name
- if( this.symbol === undefined || this.name === undefined ){
+ if( typeof qubitFirstIndex !== 'number' ) qubitFirstIndex = 0
+ if( typeof qubitLastIndex !== 'number' && typeof qubitRange !== 'number' ) qubitLastIndex = this.bandwidth
+ if( typeof qubitLastIndex !== 'number' && typeof qubitRange === 'number' ) qubitLastIndex = qubitFirstIndex + qubitRange
+ else if( typeof qubitLastIndex === 'number' && typeof qubitRange !== 'number' ) qubitRange = qubitLastIndex - qubitFirstIndex
+ else return logger.error( `Circuit attempted to copy a circuit but could not understand what qubits to copy.` )
- const found = Object.values( Q.Qubit.constants ).find( function( qubit ){
+ if( typeof momentFirstIndex !== 'number' ) momentFirstIndex = 0
+ if( typeof momentLastIndex !== 'number' && typeof momentRange !== 'number' ) momentLastIndex = this.timewidth
+ if( typeof momentLastIndex !== 'number' && typeof momentRange === 'number' ) momentLastIndex = momentFirstIndex + momentRange
+ else if( typeof momentLastIndex === 'number' && typeof momentRange !== 'number' ) momentRange = momentLastIndex - momentFirstIndex
+ else return logger.error( `Circuit attempted to copy a circuit but could not understand what moments to copy.` )
- return (
+ logger.log( 0.8,
+
+ '\nCircuit copy operation:',
+ '\n\n qubitFirstIndex', qubitFirstIndex,
+ '\n qubitLastIndex ', qubitLastIndex,
+ '\n qubitRange ', qubitRange,
+ '\n\n momentFirstIndex', momentFirstIndex,
+ '\n momentLastIndex ', momentLastIndex,
+ '\n momentRange ', momentRange,
+ '\n\n'
+ )
- a.isEqualTo( qubit.alpha ) &&
- b.isEqualTo( qubit.beta )
- )
- })
- if( found === undefined ){
+ return {
- this.symbol = '?'
- this.name = 'Unnamed'
+ qubitFirstIndex,
+ qubitRange,
+ qubitLastIndex,
+ momentFirstIndex,
+ momentRange,
+ momentLastIndex
}
- else {
+ },
- if( this.symbol === undefined ) this.symbol = found.symbol
- if( this.name === undefined ) this.name = found.name
- }
- }
-}
-Q.Qubit.prototype = Object.create( Q.Matrix.prototype )
-Q.Qubit.prototype.constructor = Q.Qubit
+ copy: function( options, isACutOperation ){
+ const original = this
+ let {
+ registerFirstIndex,
+ registerRange,
+ registerLastIndex,
+ momentFirstIndex,
+ momentRange,
+ momentLastIndex
-Object.assign( Q.Qubit, {
+ } = this.determineRanges( options )
- index: 0,
- help: function(){ return Q.help( this )},
- constants: {},
- createConstant: Q.createConstant,
- createConstants: Q.createConstants,
-
+ const copy = new Circuit( registerRange, momentRange )
+ original.operations
+ .filter( function( operation ){
+ return ( operation.registerIndices.every( function( registerIndex ){
- findBy: function( key, value ){
+ return (
- return (
-
- Object
- .values( Q.Qubit.constants )
- .find( function( item ){
+ operation.momentIndex >= momentFirstIndex &&
+ operation.momentIndex < momentLastIndex &&
+ operation.registerIndex >= registerFirstIndex &&
+ operation.registerIndex < registerLastIndex
+ )
+ }))
+ })
+ .forEach( function( operation ){
- if( typeof value === 'string' &&
- typeof item[ key ] === 'string' ){
+ const adjustedRegisterIndices = operation.registerIndices.map( function( registerIndex ){
- return value.toLowerCase() === item[ key ].toLowerCase()
- }
- return value === item[ key ]
+ return registerIndex - registerFirstIndex
})
- )
- },
- findBySymbol: function( symbol ){
+ copy.set$(
- return Q.Qubit.findBy( 'symbol', symbol )
- },
- findByName: function( name ){
+ operation.gate,
+ 1 + m - momentFirstIndex,
+ adjustedRegisterIndices
+ )
+ })
- return Q.Qubit.findBy( 'name', name )
- },
- findByBeta: function( beta ){
- if( beta instanceof Q.ComplexNumber === false ){
+ // The cut$() operation just calls copy()
+ // with the following boolean set to true.
+ // If this is a cut we need to
+ // replace all gates in this area with identity gates.
- beta = new Q.ComplexNumber( beta )
- }
- return Object.values( Q.Qubit.constants ).find( function( qubit ){
+ // UPDATE !!!!
+ // will come back to fix!!
+ // with new style it's now just a matter of
+ // splicing out these out of circuit.operations
- return qubit.beta.isEqualTo( beta )
- })
- },
- areEqual: function( qubit0, qubit1 ){
- return (
+
+ if( isACutOperation === true ){
- qubit0.alpha.isEqualTo( qubit1.alpha ) &&
- qubit0.beta.isEqualTo( qubit1.beta )
- )
- },
- collapse: function( qubit ){
+ /*
+ for( let m = momentFirstIndex; m < momentLastIndex; m ++ ){
- const
- alpha2 = Math.pow( qubit.alpha.absolute(), 2 ),
- beta2 = Math.pow( qubit.beta.absolute(), 2 ),
- randomNumberRange = Math.pow( 2, 32 ) - 1,
- randomNumber = new Uint32Array( 1 )
-
- // console.log( 'alpha^2', alpha2 )
- // console.log( 'beta^2', beta2 )
- window.crypto.getRandomValues( randomNumber )
- const randomNumberNormalized = randomNumber / randomNumberRange
- if( randomNumberNormalized <= alpha2 ){
+ original.moments[ m ] = new Array( original.bandwidth )
+ .fill( 0 )
+ .map( function( qubit, q ){
+
+ return {
- return new Q.Qubit( 1, 0 )
+ gate: Q.Gate.IDENTITY,
+ registerIndices: [ q ]
+ }
+ })
+ }*/
}
- else return new Q.Qubit( 0, 1 )
+ return copy
},
- applyGate: function( qubit, gate, ...args ){
-
- `
- This is means of inverting what comes first:
- the Gate or the Qubit?
- If the Gate only operates on a single qubit,
- then it doesn’t matter and we can do this:
- `
+ cut$: function( options ){
- if( gate instanceof Q.Gate === false ) return Q.error( `Q.Qubit attempted to apply something that was not a gate to this qubit #${ qubit.index }.` )
- else return gate.applyToQubit( qubit, ...args )
+ return this.copy( options, true )
},
- toText: function( qubit ){
- //return `|${qubit.beta.toText()}⟩`
- return qubit.alpha.toText() +'\n'+ qubit.beta.toText()
- },
- toStateVectorText: function( qubit ){
- return `|${ qubit.beta.toText() }⟩`
- },
- toStateVectorHtml: function( qubit ){
- return `${ qubit.beta.toText() } `
- },
- // This code was a pain in the ass to figure out.
- // I’m not fluent in trigonometry
- // and none of the quantum primers actually lay out
- // how to convert arbitrary qubit states
- // to Bloch Sphere representation.
- // Oh, they provide equivalencies for specific states, sure.
- // I hope this is useful to you
- // unless you are porting this to a terrible language
- // like C# or Java or something ;)
-
- toBlochSphere: function( qubit ){
- `
- Based on this qubit’s state return the
- Polar angle θ (theta),
- azimuth angle Ï• (phi),
- Bloch vector,
- corrected surface coordinate.
+ /*
- https://en.wikipedia.org/wiki/Bloch_sphere
- `
- // Polar angle θ (theta).
- const theta = Q.ComplexNumber.arcCosine( qubit.alpha ).multiply( 2 )
- if( isNaN( theta.real )) theta.real = 0
- if( isNaN( theta.imaginary )) theta.imaginary = 0
+ If covers all moments for 1 or more qubits then
+ 1. go through each moment and remove those qubits
+ 2. remove hanging operations. (right?? don’t want them?)
-
- // Azimuth angle Ï• (phi).
-
- const phi = Q.ComplexNumber.log(
- qubit.beta.divide( Q.ComplexNumber.sine( theta.divide( 2 )))
- )
- .divide( Q.ComplexNumber.I )
- if( isNaN( phi.real )) phi.real = 0
- if( isNaN( phi.imaginary )) phi.imaginary = 0
-
- // Bloch vector.
- const vector = {
-
- x: Q.ComplexNumber.sine( theta ).multiply( Q.ComplexNumber.cosine( phi )).real,
- y: Q.ComplexNumber.sine( theta ).multiply( Q.ComplexNumber.sine( phi )).real,
- z: Q.ComplexNumber.cosine( theta ).real
- }
+ */
+ spliceCut$: function( options ){
- // Bloch vector’s axes are wonked.
- // Let’s “correct†them for use with Three.js, etc.
+ let {
- const position = {
+ qubitFirstIndex,
+ qubitRange,
+ qubitLastIndex,
+ momentFirstIndex,
+ momentRange,
+ momentLastIndex
- x: vector.y,
- y: vector.z,
- z: vector.x
- }
+ } = this.determineRanges( options )
- return {
+ // Only three options are valid:
+ // 1. Selection area covers ALL qubits for a series of moments.
+ // 2. Selection area covers ALL moments for a seriies of qubits.
+ // 3. Both of the above (splice the entire circuit).
- // Wow does this make tweening easier down the road.
+ if( qubitRange !== this.bandwidth &&
+ momentRange !== this.timewidth ){
- alphaReal: qubit.alpha.real,
- alphaImaginary: qubit.alpha.imaginary,
- betaReal: qubit.beta.real,
- betaImaginary: qubit.beta.imaginary,
+ return logger.error( `Circuit attempted to splice circuit #${this.index} by an area that did not include all qubits _or_ all moments.` )
+ }
- // Ummm... I’m only returnig the REAL portions. Please forgive me!
+ // If the selection area covers all qubits for 1 or more moments
+ // then splice the moments array.
+
+ if( qubitRange === this.bandwidth ){
- theta: theta.real,
- phi: phi.real,
- vector, // Wonked YZX vector for maths because maths.
- position// Un-wonked XYZ for use by actual 3D engines.
- }
- },
- fromBlochVector: function( x, y, z ){
+ // We cannot use Array.prototype.splice() for this
+ // because we need a DEEP copy of the array
+ // and splice() will only make a shallow copy.
+
+ this.moments = this.moments.reduce( function( accumulator, moment, m ){
- //basically from a Pauli Rotation
- }
+ if( m < momentFirstIndex - 1 || m >= momentLastIndex - 1 ) accumulator.push( moment )
+ return accumulator
+
+ }, [])
+ this.timewidth -= momentRange
-})
+ //@@ And how do we implement splicePaste$() here?
+ }
+ // If the selection area covers all moments for 1 or more qubits
+ // then iterate over each moment and remove those qubits.
+
+ if( momentRange === this.timewidth ){
-Q.Qubit.createConstants(
+ // First, let’s splice the inputs array.
+ this.inputs.splice( qubitFirstIndex, qubitRange )
+ //@@ this.inputs.splice( qubitFirstIndex, qubitRange, qubitsToPaste?? )
+
- // Opposing pairs:
- // |H⟩ and |V⟩
- // |D⟩ and |A⟩
- // |R⟩ and |L⟩
+ // Now we can make the proper adjustments
+ // to each of our moments.
- 'HORIZONTAL', new Q.Qubit( 1, 0, 'H', 'Horizontal' ),// ZERO.
- 'VERTICAL', new Q.Qubit( 0, 1, 'V', 'Vertical' ),// ONE.
- 'DIAGONAL', new Q.Qubit( Math.SQRT1_2, Math.SQRT1_2, 'D', 'Diagonal' ),
- 'ANTI_DIAGONAL', new Q.Qubit( Math.SQRT1_2, -Math.SQRT1_2, 'A', 'Anti-diagonal' ),
- 'RIGHT_HAND_CIRCULAR_POLARIZED', new Q.Qubit( Math.SQRT1_2, new Q.ComplexNumber( 0, -Math.SQRT1_2 ), 'R', 'Right-hand Circular Polarized' ),// RHCP
- 'LEFT_HAND_CIRCULAR_POLARIZED', new Q.Qubit( Math.SQRT1_2, new Q.ComplexNumber( 0, Math.SQRT1_2 ), 'L', 'Left-hand Circular Polarized' ) // LHCP
-)
+ this.moments = this.moments.map( function( operations ){
+
+ // Remove operations that pertain to the removed qubits.
+ // Renumber the remaining operations’ qubitIndices.
+
+ return operations.reduce( function( accumulator, operation ){
+ if( operation.qubitIndices.every( function( index ){
+ return index < qubitFirstIndex || index >= qubitLastIndex
+
+ })) accumulator.push( operation )
+ return accumulator
+
+ }, [])
+ .map( function( operation ){
-Object.assign( Q.Qubit.prototype, {
+ operation.qubitIndices = operation.qubitIndices.map( function( index ){
- copy$: function( matrix ){
+ return index >= qubitLastIndex ? index - qubitRange : index
+ })
+ return operation
+ })
+ })
+ this.bandwidth -= qubitRange
+ }
+
- if( Q.Matrix.isMatrixLike( matrix ) !== true )
- return Q.error( `Q.Qubit attempted to copy something that was not a matrix in this qubit #${qubit.index}.`, this )
+ // Final clean up.
- if( Q.Matrix.haveEqualDimensions( matrix, this ) !== true )
- return Q.error( `Q.Qubit cannot copy matrix#${matrix.index} of dimensions ${matrix.columns.length}x${matrix.rows.length} in to this qubit #${this.index} of dimensions ${this.columns.length}x${this.rows.length} because their dimensions do not match.`, this )
+ this.removeHangingOperations$()
+ this.fillEmptyOperations$()
- const that = this
- matrix.rows.forEach( function( row, r ){
- row.forEach( function( n, c ){
-
- that.rows[ r ][ c ] = n
- })
- })
- this.dirac = matrix.dirac
- return this
+ return this// Or should we return the cut area?!
},
- clone: function(){
+ splicePaste$: function(){
- return new Q.Qubit( this.alpha, this.beta )
- },
- isEqualTo: function( otherQubit ){
- return Q.Qubit.areEqual( this, otherQubit )// Returns a Boolean, breaks function chaining!
},
- collapse: function(){
+
- return Q.Qubit.collapse( this )
- },
- applyGate: function( gate, ...args ){
- return Q.Qubit.applyGate( this, gate, ...args )
- },
- toText: function(){
- return Q.Qubit.toText( this )// Returns a String, breaks function chaining!
- },
- toStateVectorText: function(){
- return Q.Qubit.toStateVectorText( this )// Returns a String, breaks function chaining!
- },
- toStateVectorHtml: function(){
+ // This is where “hanging operations” get interesting!
+ // when you paste one circuit in to another
+ // and that clipboard circuit has hanging operations
+ // those can find a home in the circuit its being pasted in to!
- return Q.Qubit.toStateVectorHtml( this )// Returns a String, breaks function chaining!
- },
- toBlochSphere: function(){
- return Q.Qubit.toBlochSphere( this )// Returns an Object, breaks function chaining!
- },
- collapse$: function(){
-
- return this.copy$( Q.Qubit.collapse( this ))
- },
- applyGate$: function( gate ){
+ paste$: function( other, atMoment = 0, atQubit = 0, shouldClean = true ){
- return this.copy$( Q.Qubit.applyGate( this, gate ))
- },
-})
+ const scope = this
+ this.timewidth = Math.max( this.timewidth, atMoment + other.timewidth )
+ this.bandwidth = Math.max( this.bandwidth, atQubit + other.bandwidth )
+ this.ensureMomentsAreReady$()
+ this.fillEmptyOperations$()
+ other.moments.forEach( function( moment, m ){
+ moment.forEach( function( operation ){
+ //console.log( 'past over w this:', m + atMoment, operation )
+ scope.set$(
-// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+ operation.gate,
+ m + atMoment + 1,
+ operation.qubitIndices.map( function( qubitIndex ){
+ return qubitIndex + atQubit
+ })
+ )
+ })
+ })
+ if( shouldClean ) this.removeHangingOperations$()
+ this.fillEmptyOperations$()
+ return this
+ },
+ pasteInsert$: function( other, atMoment, atQubit ){
+ // if( other.alphandwidth !== this.bandwidth &&
+ // other.timewidth !== this.timewidth ) return error( 'Circuit attempted to pasteInsert Circuit A', other, 'in to circuit B', this, 'but neither their bandwidth or timewidth matches.' )
+
-Q.Gate = function( params ){
- Object.assign( this, params )
- this.index = Q.Gate.index ++
-
- if( typeof this.symbol !== 'string' ) this.symbol = '?'
- if( typeof this.symbolAmazonBraket !== 'string' ) this.symbolAmazonBraket = this.symbol.toLowerCase()
- const parameters = Object.assign( {}, params.parameters )
- this.parameters = parameters
-
- // We use symbols as unique identifiers
- // among gate CONSTANTS
- // so if you use the same symbol for a non-constant
- // that’s not a deal breaker
- // but it is good to know.
+ if( shouldClean ) this.removeHangingOperations$()
+ this.fillEmptyOperations$()
+ return this
- const
- scope = this,
- foundConstant = Object
- .values( Q.Gate.constants )
- .find( function( gate ){
+ },
+ expand$: function(){
- return gate.symbol === scope.symbol
- })
+ // expand either bandwidth or timewidth, fill w identity
- //Muting this warning in order to have parameterized gates (that don't totally mess with the constants), we need
- //to make clones of the constants...a lot if you're using a lot of parameterized gates. Warning gets annoying :/.
- // if( foundConstant ){
-
- // Q.warn( `Q.Gate is creating a new instance, #${ this.index }, that uses the same symbol as a pre-existing Gate constant:`, foundConstant )
- // }
- if( typeof this.name !== 'string' ) this.name = 'Unknown'
- if( typeof this.nameCss !== 'string' ) this.nameCss = 'unknown'
+ this.fillEmptyOperations$()
+ return thiis
+ },
- // If our gate’s matrix is to be
- // dynamically created or updated
- // then we ouoght to do that now.
- if( typeof this.updateMatrix$ === 'function' ) this.updateMatrix$()
- // Every gate must have an applyToQubit method.
- // If it doesn’t exist we’ll create one
- // based on whether a matrix property exists or not.
- if( typeof this.applyToQubit !== 'function' ){
- if( this.matrix instanceof Q.Matrix === true ){
-
- this.applyToQubit = function( qubit ){
+ trim$: function( options ){
- return new Q.Qubit( this.matrix.multiply( qubit ))
- }
- }
- else {
+ `
+ Edit this circuit by trimming off moments, qubits, or both.
+ We could have implemented trim$() as a wrapper around copy$(),
+ similar to how cut$ is a wrapper around copy$().
+ But this operates on the existing circuit
+ instead of returning a new one and returning that.
+ `
- this.applyToQubit = function( qubit ){ return qubit }
- }
- }
-}
+ let {
+ qubitFirstIndex,
+ qubitRange,
+ qubitLastIndex,
+ momentFirstIndex,
+ momentRange,
+ momentLastIndex
+ } = this.determineRanges( options )
-Object.assign( Q.Gate, {
+ // First, trim the moments down to desired size.
- index: 0,
- constants: {},
- createConstant: Q.createConstant,
- createConstants: Q.createConstants,
- findBy: function( key, value ){
+ this.moments = this.moments.slice( momentFirstIndex, momentLastIndex )
+ this.timewidth = momentRange
- return (
-
- Object
- .values( Q.Gate.constants )
- .find( function( item ){
- if( typeof value === 'string' &&
- typeof item[ key ] === 'string' ){
+ // Then, trim the bandwidth down.
- return value.toLowerCase() === item[ key ].toLowerCase()
- }
- return value === item[ key ]
- })
- )
- },
- findBySymbol: function( symbol ){
+ this.inputs = this.inputs.slice( qubitFirstIndex, qubitLastIndex )
+ this.bandwidth = qubitRange
- return Q.Gate.findBy( 'symbol', symbol )
- },
- findByName: function( name ){
- return Q.Gate.findBy( 'name', name )
+ // Finally, remove all gates where
+ // gate’s qubit indices contain an index < qubitFirstIndex,
+ // gate’s qubit indices contain an index > qubitLastIndex,
+ // and fill those holes with Identity gates.
+
+ this.removeHangingOperations$()
+ this.fillEmptyOperations$()
+
+ return this
}
})
-Object.assign( Q.Gate.prototype, {
- clone: function( params ){
- return new Q.Gate( Object.assign( {}, this, params ))
- },
- applyToQubits: function(){
- return Array.from( arguments ).map( this.applyToQubit.bind( this ))
- },
- set$: function( key, value ){
+// Against my predilection for verbose clarity...
+// I offer you super short convenience methods
+// that do NOT use the $ suffix to delcare they are destructive.
+// Don’t shoot your foot off.
+Object.entries( Gate.constants ).forEach( function( entry ){
- this[ key ] = value
- return this
- },
- setSymbol$: function( value ){
+ const
+ gateConstantName = entry[ 0 ],
+ gate = entry[ 1 ],
+ set$ = function( momentIndex, registerIndexOrIndices ){
- return this.set$( 'symbol', value )
+ this.set$( gate, momentIndex, registerIndexOrIndices )
+ return this
}
+ Circuit.prototype[ gateConstantName ] = set$
+ Circuit.prototype[ gate.symbol ] = set$
+ Circuit.prototype[ gate.symbol.toLowerCase() ] = set$
})
+/*
+const bells = [
-Q.Gate.createConstants(
+
+ // Verbose without shortcuts.
+
+ new Circuit( 2, 2 )
+ .set$( Q.Gate.HADAMARD, 1, [ 1 ])
+ .set$( Q.Gate.PAULI_X, 2, [ 1 , 2 ]),
+
+ new Circuit( 2, 2 )
+ .set$( Q.Gate.HADAMARD, 1, 1 )
+ .set$( Q.Gate.PAULI_X, 2, [ 1 , 2 ]),
+
+
+ // Uses Q.Gate.findBySymbol() to lookup gates.
+
+ new Circuit( 2, 2 )
+ .set$( 'H', 1, [ 1 ])
+ .set$( 'X', 2, [ 1 , 2 ]),
+
+ new Circuit( 2, 2 )
+ .set$( 'H', 1, 1 )
+ .set$( 'X', 2, [ 1 , 2 ]),
+
+
+ // Convenience gate functions -- constant name.
+
+ new Circuit( 2, 2 )
+ .HADAMARD( 1, [ 1 ])
+ .PAULI_X( 2, [ 1, 2 ]),
+
+ new Circuit( 2, 2 )
+ .HADAMARD( 1, 1 )
+ .PAULI_X( 2, [ 1, 2 ]),
+
+
+ // Convenience gate functions -- uppercase symbol.
+
+ new Circuit( 2, 2 )
+ .H( 1, [ 1 ])
+ .X( 2, [ 1, 2 ]),
+
+ new Circuit( 2, 2 )
+ .H( 1, 1 )
+ .X( 2, [ 1, 2 ]),
+
+
+ // Convenience gate functions -- lowercase symbol.
+
+ new Circuit( 2, 2 )
+ .h( 1, [ 1 ])
+ .x( 2, [ 1, 2 ]),
+
+ new Circuit( 2, 2 )// Perhaps the closest to Braket style.
+ .h( 1, 1 )
+ .x( 2, [ 1, 2 ]),
+
+
+ // Q function -- bandwidth / timewidth arguments.
+
+ Q( 2, 2 )
+ .h( 1, [ 1 ])
+ .x( 2, [ 1, 2 ]),
+
+ Q( 2, 2 )
+ .h( 1, 1 )
+ .x( 2, [ 1, 2 ]),
+
+
+ // Q function -- text block argument
+ // with operation symbols
+ // and operation component IDs.
+
+ Q`
+ H-X.0#0
+ I-X.0#1`,
+
+
+ // Q function -- text block argument
+ // using only component IDs
+ // (ie. no operation symbols)
+ // because the operation that the
+ // components should belong to is NOT ambiguous.
+
+ Q`
+ H-X#0
+ I-X#1`,
+
+
+ // Q function -- text block argument
+ // as above, but using only whitespace
+ // to partition between moments.
+
+ Q`
+ H X#0
+ I X#1`
+],
+bellsAreEqual = !!bells.reduce( function( a, b ){
+
+ return a.toText() === b.toText() ? a : NaN
+
+})
+if( bellsAreEqual ){
+
+ console.log( `\n\nYES. All of ${ bells.length } our “Bell” circuits are equal.\n\n`, bells )
+}
+*/
+
+
+
+
+
+
+
+Circuit.createConstants(
+
+ 'BELL', new Circuit.fromText(`
+
+ H X#0
+ I X#1
+ `),
+ // 'GROVER', Q`
+
+ // H X *#0 X#0 I X#0 I I I X#0 I I I X#0 I X H X I *#0
+ // H X I X#1 *#0 X#1 *#0 X#0 I I I X#0 X I H X I I I I
+ // H X I I I I I X#1 *#0 X#1 *#0 X#1 *#0 X#1 I *#0 X H X I
+ // H X *#1 I *#1 I *#1 I *#1 I *#1 I *#1 I I *#1 X H X *#1
+ // `
+
+ //https://docs.microsoft.com/en-us/quantum/concepts/circuits?view=qsharp-preview
+ // 'TELEPORT', Q.(`
+
+ // I-I--H-M---v
+ // H-C0-I-M-v-v
+ // I-C1-I-I-X-Z-
+ // `)
+)
+
+
+module.exports = {Circuit};
+
+
+},{"./Logging":3,"./Math-Functions":4,"./Misc":5,"./Q-ComplexNumber":7,"./Q-Gate":8,"./Q-History":9,"./Q-Matrix":10,"./Q-Qubit":11}],7:[function(require,module,exports){
+// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+
+const { warn, error, help } = require('./Logging');
+const mathf = require('./Math-Functions');
+const misc = require('./Misc');
+const EPSILON = misc.constants.EPSILON;
+ComplexNumber = function (real, imaginary) {
+ `
+ The set of “real numbers” (ℝ) contains any number that can be expressed
+ along an infinite timeline. https://en.wikipedia.org/wiki/Real_number
+
+ … -3 -2 -1 0 +1 +2 +3 …
+ ┄───┴───┴───┴───┴───┴─┬─┴──┬┴┬──┄
+ √2 𝒆 π
+
+
+ Meanwhile, “imaginary numbers” (𝕀) consist of a real (ℝ) multiplier and
+ the symbol 𝒊, which is the impossible solution to the equation 𝒙² = −1.
+ Note that no number when multiplied by itself can ever result in a
+ negative product, but the concept of 𝒊 gives us a way to reason around
+ this imaginary scenario nonetheless.
+ https://en.wikipedia.org/wiki/Imaginary_number
+
+ … -3𝒊 -2𝒊 -1𝒊 0𝒊 +1𝒊 +2𝒊 +3𝒊 …
+ ┄───┴───┴───┴───┴───┴───┴───┴───┄
+
+
+ A “complex number“ (ℂ) is a number that can be expressed in the form
+ 𝒂 + 𝒃𝒊, where 𝒂 is the real component (ℝ) and 𝒃𝒊 is the imaginary
+ component (𝕀). https://en.wikipedia.org/wiki/Complex_number
+
+
+ Operation functions on ComplexNumber instances generally accept as
+ arguments both sibling instances and pure Number instances, though the
+ value returned is always an instance of ComplexNumber.
+
+ `;
+
+ if (real instanceof ComplexNumber) {
+ imaginary = real.imaginary;
+ real = real.real;
+ warn(
+ "ComplexNumber tried to create a new instance with an argument that is already a ComplexNumber — and that’s weird!"
+ );
+ } else if (real === undefined) real = 0;
+ if (imaginary === undefined) imaginary = 0;
+ if (
+ (ComplexNumber.isNumberLike(real) !== true && isNaN(real) !== true) ||
+ (ComplexNumber.isNumberLike(imaginary) !== true &&
+ isNaN(imaginary) !== true)
+ )
+ return error(
+ "ComplexNumber attempted to create a new instance but the arguments provided were not actual numbers."
+ );
+
+ this.real = real;
+ this.imaginary = imaginary;
+ this.index = ComplexNumber.index++;
+};
+
+Object.assign(ComplexNumber, {
+ index: 0,
+ help: function () {
+ return help(this);
+ },
+ constants: {},
+ createConstant: function (key, value) {
+ //Object.freeze( value )
+ this[key] = value;
+ // Object.defineProperty( this, key, {
+
+ // value,
+ // writable: false
+ // })
+ // Object.defineProperty( this.constants, key, {
+
+ // value,
+ // writable: false
+ // })
+ this.constants[key] = this[key];
+ Object.freeze(this[key]);
+ },
+ createConstants: function () {
+ if (arguments.length % 2 !== 0) {
+ return error(
+ "Q attempted to create constants with invalid (KEY, VALUE) pairs."
+ );
+ }
+ for (let i = 0; i < arguments.length; i += 2) {
+ this.createConstant(arguments[i], arguments[i + 1]);
+ }
+ },
+
+ toText: function (rNumber, iNumber, roundToDecimal, padPositive) {
+ // Should we round these numbers?
+ // Our default is yes: to 3 digits.
+ // Otherwise round to specified decimal.
+
+ if (typeof roundToDecimal !== "number") roundToDecimal = 3;
+ const factor = Math.pow(10, roundToDecimal);
+ rNumber = Math.round(rNumber * factor) / factor;
+ iNumber = Math.round(iNumber * factor) / factor;
+
+ // Convert padPositive
+ // from a potential Boolean
+ // to a String.
+ // If we don’t receive a FALSE
+ // then we’ll pad the positive numbers.
+
+ padPositive = padPositive === false ? "" : " ";
+
+ // We need the absolute values of each.
+
+ let rAbsolute = Math.abs(rNumber),
+ iAbsolute = Math.abs(iNumber);
+
+ // And an absolute value string.
+
+ let rText = rAbsolute.toString(),
+ iText = iAbsolute.toString();
+
+ // Is this an IMAGINARY-ONLY number?
+ // Don’t worry: -0 === 0.
+
+ if (rNumber === 0) {
+ if (iNumber === Infinity) return padPositive + "∞i";
+ if (iNumber === -Infinity) return "-∞i";
+ if (iNumber === 0) return padPositive + "0";
+ if (iNumber === -1) return "-i";
+ if (iNumber === 1) return padPositive + "i";
+ if (iNumber >= 0) return padPositive + iText + "i";
+ if (iNumber < 0) return "-" + iText + "i";
+ return iText + "i"; // NaN
+ }
+
+ // This number contains a real component
+ // and may also contain an imaginary one as well.
+
+ if (rNumber === Infinity) rText = padPositive + "∞";
+ else if (rNumber === -Infinity) rText = "-∞";
+ else if (rNumber >= 0) rText = padPositive + rText;
+ else if (rNumber < 0) rText = "-" + rText;
+
+ if (iNumber === Infinity) return rText + " + ∞i";
+ if (iNumber === -Infinity) return rText + " - ∞i";
+ if (iNumber === 0) return rText;
+ if (iNumber === -1) return rText + " - i";
+ if (iNumber === 1) return rText + " + i";
+ if (iNumber > 0) return rText + " + " + iText + "i";
+ if (iNumber < 0) return rText + " - " + iText + "i";
+ return rText + " + " + iText + "i"; // NaN
+ },
+
+ isNumberLike: function (n) {
+ return isNaN(n) === false && (typeof n === "number" || n instanceof Number);
+ },
+ isNaN: function (n) {
+ return isNaN(n.real) || isNaN(n.imaginary);
+ },
+ isZero: function (n) {
+ return (
+ (n.real === 0 || n.real === -0) &&
+ (n.imaginary === 0 || n.imaginary === -0)
+ );
+ },
+ isFinite: function (n) {
+ return isFinite(n.real) && isFinite(n.imaginary);
+ },
+ isInfinite: function (n) {
+ return !(this.isNaN(n) || this.isFinite(n));
+ },
+ areEqual: function (a, b) {
+ return ComplexNumber.operate(
+ "areEqual",
+ a,
+ b,
+ function (a, b) {
+ return Math.abs(a - b) < EPSILON;
+ },
+ function (a, b) {
+ return (
+ Math.abs(a - b.real) < EPSILON && Math.abs(b.imaginary) < EPSILON
+ );
+ },
+ function (a, b) {
+ return (
+ Math.abs(a.real - b) < EPSILON && Math.abs(a.imaginary) < EPSILON
+ );
+ },
+ function (a, b) {
+ return (
+ Math.abs(a.real - b.real) < EPSILON &&
+ Math.abs(a.imaginary - b.imaginary) < EPSILON
+ );
+ }
+ );
+ },
+
+ absolute: function (n) {
+ return mathf.hypotenuse(n.real, n.imaginary);
+ },
+ conjugate: function (n) {
+ return new ComplexNumber(n.real, n.imaginary * -1);
+ },
+ operate: function (
+ name,
+ a,
+ b,
+ numberAndNumber,
+ numberAndComplex,
+ complexAndNumber,
+ complexAndComplex
+ ) {
+ if (ComplexNumber.isNumberLike(a)) {
+ if (ComplexNumber.isNumberLike(b)) return numberAndNumber(a, b);
+ else if (b instanceof ComplexNumber) return numberAndComplex(a, b);
+ else
+ return error(
+ "ComplexNumber attempted to",
+ name,
+ "with the number",
+ a,
+ "and something that is neither a Number or ComplexNumber:",
+ b
+ );
+ } else if (a instanceof ComplexNumber) {
+ if (ComplexNumber.isNumberLike(b)) return complexAndNumber(a, b);
+ else if (b instanceof ComplexNumber) return complexAndComplex(a, b);
+ else
+ return error(
+ "ComplexNumber attempted to",
+ name,
+ "with the complex number",
+ a,
+ "and something that is neither a Number or ComplexNumber:",
+ b
+ );
+ } else
+ return error(
+ "ComplexNumber attempted to",
+ name,
+ "with something that is neither a Number or ComplexNumber:",
+ a
+ );
+ },
+
+ sine: function (n) {
+ const a = n.real,
+ b = n.imaginary;
+
+ return new ComplexNumber(
+ Math.sin(a) * mathf.hyperbolicCosine(b),
+ Math.cos(a) * mathf.hyperbolicSine(b)
+ );
+ },
+ cosine: function (n) {
+ const a = n.real,
+ b = n.imaginary;
+
+ return new ComplexNumber(
+ Math.cos(a) * mathf.hyperbolicCosine(b),
+ -Math.sin(a) * mathf.hyperbolicSine(b)
+ );
+ },
+ arcCosine: function (n) {
+ const a = n.real,
+ b = n.imaginary,
+ t1 = ComplexNumber.squareRoot(
+ new ComplexNumber(b * b - a * a + 1, a * b * -2)
+ ),
+ t2 = ComplexNumber.log(new ComplexNumber(t1.real - b, t1.imaginary + a));
+ return new ComplexNumber(Math.PI / 2 - t2.imaginary, t2.real);
+ },
+ arcTangent: function (n) {
+ const a = n.real,
+ b = n.imaginary;
+
+ if (a === 0) {
+ if (b === 1) return new ComplexNumber(0, Infinity);
+ if (b === -1) return new ComplexNumber(0, -Infinity);
+ }
+
+ const d = a * a + (1 - b) * (1 - b),
+ t = ComplexNumber.log(
+ new ComplexNumber((1 - b * b - a * a) / d, (a / d) * -2)
+ );
+ return new ComplexNumber(t.imaginary / 2, t.real / 2);
+ },
+
+ power: function (a, b) {
+ if (ComplexNumber.isNumberLike(a)) a = new ComplexNumber(a);
+ if (ComplexNumber.isNumberLike(b)) b = new ComplexNumber(b);
+
+ // Anything raised to the Zero power is 1.
+
+ if (b.isZero()) return ComplexNumber.ONE;
+
+ // Zero raised to any power is 0.
+ // Note: What happens if b.real is zero or negative?
+ // What happens if b.imaginary is negative?
+ // Do we really need those conditionals??
+
+ if (a.isZero() && b.real > 0 && b.imaginary >= 0) {
+ return ComplexNumber.ZERO;
+ }
+
+ // If our exponent is Real (has no Imaginary component)
+ // then we’re really just raising to a power.
+
+ if (b.imaginary === 0) {
+ if (a.real >= 0 && a.imaginary === 0) {
+ return new ComplexNumber(Math.pow(a.real, b.real), 0);
+ } else if (a.real === 0) {
+ // If our base is Imaginary (has no Real component).
+
+ switch (((b.real % 4) + 4) % 4) {
+ case 0:
+ return new ComplexNumber(Math.pow(a.imaginary, b.real), 0);
+ case 1:
+ return new ComplexNumber(0, Math.pow(a.imaginary, b.real));
+ case 2:
+ return new ComplexNumber(-Math.pow(a.imaginary, b.real), 0);
+ case 3:
+ return new ComplexNumber(0, -Math.pow(a.imaginary, b.real));
+ }
+ }
+ }
+
+ const arctangent2 = Math.atan2(a.imaginary, a.real),
+ logHypotenuse = mathf.logHypotenuse(a.real, a.imaginary),
+ x = Math.exp(b.real * logHypotenuse - b.imaginary * arctangent2),
+ y = b.imaginary * logHypotenuse + b.real * arctangent2;
+
+ return new ComplexNumber(x * Math.cos(y), x * Math.sin(y));
+ },
+ squareRoot: function (a) {
+ const result = new ComplexNumber(0, 0),
+ absolute = ComplexNumber.absolute(a);
+
+ if (a.real >= 0) {
+ if (a.imaginary === 0) {
+ result.real = Math.sqrt(a.real); // and imaginary already equals 0.
+ } else {
+ result.real = Math.sqrt(2 * (absolute + a.real)) / 2;
+ }
+ } else {
+ result.real = Math.abs(a.imaginary) / Math.sqrt(2 * (absolute - a.real));
+ }
+ if (a.real <= 0) {
+ result.imaginary = Math.sqrt(2 * (absolute - a.real)) / 2;
+ } else {
+ result.imaginary =
+ Math.abs(a.imaginary) / Math.sqrt(2 * (absolute + a.real));
+ }
+ if (a.imaginary < 0) result.imaginary *= -1;
+ return result;
+ },
+ log: function (a) {
+ return new ComplexNumber(
+ mathf.logHypotenuse(a.real, a.imaginary),
+ Math.atan2(a.imaginary, a.real)
+ );
+ },
+ multiply: function (a, b) {
+ return ComplexNumber.operate(
+ "multiply",
+ a,
+ b,
+ function (a, b) {
+ return new ComplexNumber(a * b);
+ },
+ function (a, b) {
+ return new ComplexNumber(a * b.real, a * b.imaginary);
+ },
+ function (a, b) {
+ return new ComplexNumber(a.real * b, a.imaginary * b);
+ },
+ function (a, b) {
+ // FOIL Method that shit.
+ // https://en.wikipedia.org/wiki/FOIL_method
+
+ const firsts = a.real * b.real,
+ outers = a.real * b.imaginary,
+ inners = a.imaginary * b.real,
+ lasts = a.imaginary * b.imaginary * -1; // Because i² = -1.
+
+ return new ComplexNumber(firsts + lasts, outers + inners);
+ }
+ );
+ },
+ divide: function (a, b) {
+ return ComplexNumber.operate(
+ "divide",
+ a,
+ b,
+ function (a, b) {
+ return new ComplexNumber(a / b);
+ },
+ function (a, b) {
+ return new ComplexNumber(a).divide(b);
+ },
+ function (a, b) {
+ return new ComplexNumber(a.real / b, a.imaginary / b);
+ },
+ function (a, b) {
+ // Ermergerd I had to look this up because it’s been so long.
+ // https://www.khanacademy.org/math/precalculus/imaginary-and-complex-numbers/complex-conjugates-and-dividing-complex-numbers/a/dividing-complex-numbers-review
+
+ const conjugate = b.conjugate(),
+ numerator = a.multiply(conjugate),
+ // The .imaginary will be ZERO for sure,
+ // so this forces a ComplexNumber.divide( Number ) ;)
+
+ denominator = b.multiply(conjugate).real;
+
+ return numerator.divide(denominator);
+ }
+ );
+ },
+ add: function (a, b) {
+ return ComplexNumber.operate(
+ "add",
+ a,
+ b,
+ function (a, b) {
+ return new ComplexNumber(a + b);
+ },
+ function (a, b) {
+ return new ComplexNumber(b.real + a, b.imaginary);
+ },
+ function (a, b) {
+ return new ComplexNumber(a.real + b, a.imaginary);
+ },
+ function (a, b) {
+ return new ComplexNumber(a.real + b.real, a.imaginary + b.imaginary);
+ }
+ );
+ },
+ subtract: function (a, b) {
+ return ComplexNumber.operate(
+ "subtract",
+ a,
+ b,
+ function (a, b) {
+ return new ComplexNumber(a - b);
+ },
+ function (a, b) {
+ return new ComplexNumber(b.real - a, b.imaginary);
+ },
+ function (a, b) {
+ return new ComplexNumber(a.real - b, a.imaginary);
+ },
+ function (a, b) {
+ return new ComplexNumber(a.real - b.real, a.imaginary - b.imaginary);
+ }
+ );
+ },
+});
+
+ComplexNumber.createConstants(
+ "ZERO",
+ new ComplexNumber(0, 0),
+ "ONE",
+ new ComplexNumber(1, 0),
+ "E",
+ new ComplexNumber(Math.E, 0),
+ "PI",
+ new ComplexNumber(Math.PI, 0),
+ "I",
+ new ComplexNumber(0, 1),
+ "EPSILON",
+ new ComplexNumber(EPSILON, EPSILON),
+ "INFINITY",
+ new ComplexNumber(Infinity, Infinity),
+ "NAN",
+ new ComplexNumber(NaN, NaN)
+);
+
+Object.assign(ComplexNumber.prototype, {
+ // NON-destructive operations.
+
+ clone: function () {
+ return new ComplexNumber(this.real, this.imaginary);
+ },
+ reduce: function () {
+ // Note: this *might* kill function chaining.
+
+ if (this.imaginary === 0) return this.real;
+ return this;
+ },
+ toText: function (roundToDecimal, padPositive) {
+ // Note: this will kill function chaining.
+
+ return ComplexNumber.toText(
+ this.real,
+ this.imaginary,
+ roundToDecimal,
+ padPositive
+ );
+ },
+
+ isNaN: function (n) {
+ return ComplexNumber.isNaN(this); // Returned boolean will kill function chaining.
+ },
+ isZero: function (n) {
+ return ComplexNumber.isZero(this); // Returned boolean will kill function chaining.
+ },
+ isFinite: function (n) {
+ return ComplexNumber.isFinite(this); // Returned boolean will kill function chaining.
+ },
+ isInfinite: function (n) {
+ return ComplexNumber.isInfinite(this); // Returned boolean will kill function chaining.
+ },
+ isEqualTo: function (b) {
+ return ComplexNumber.areEqual(this, b); // Returned boolean will kill function chaining.
+ },
+
+ absolute: function () {
+ return ComplexNumber.absolute(this); // Returned number will kill function chaining.
+ },
+ conjugate: function () {
+ return ComplexNumber.conjugate(this);
+ },
+
+ power: function (b) {
+ return ComplexNumber.power(this, b);
+ },
+ squareRoot: function () {
+ return ComplexNumber.squareRoot(this);
+ },
+ log: function () {
+ return ComplexNumber.log(this);
+ },
+ multiply: function (b) {
+ return ComplexNumber.multiply(this, b);
+ },
+ divide: function (b) {
+ return ComplexNumber.divide(this, b);
+ },
+ add: function (b) {
+ return ComplexNumber.add(this, b);
+ },
+ subtract: function (b) {
+ return ComplexNumber.subtract(this, b);
+ },
+
+ // DESTRUCTIVE operations.
+
+ copy$: function (b) {
+ if (b instanceof ComplexNumber !== true)
+ return error(
+ `ComplexNumber attempted to copy something that was not a complex number in to this complex number #${this.index}.`,
+ this
+ );
+
+ this.real = b.real;
+ this.imaginary = b.imaginary;
+ return this;
+ },
+ conjugate$: function () {
+ return this.copy$(this.conjugate());
+ },
+ power$: function (b) {
+ return this.copy$(this.power(b));
+ },
+ squareRoot$: function () {
+ return this.copy$(this.squareRoot());
+ },
+ log$: function () {
+ return this.copy$(this.log());
+ },
+ multiply$: function (b) {
+ return this.copy$(this.multiply(b));
+ },
+ divide$: function (b) {
+ return this.copy$(this.divide(b));
+ },
+ add$: function (b) {
+ return this.copy$(this.add(b));
+ },
+ subtract$: function (b) {
+ return this.copy$(this.subtract(b));
+ },
+});
+
+module.exports = { ComplexNumber };
+
+},{"./Logging":3,"./Math-Functions":4,"./Misc":5}],8:[function(require,module,exports){
+
+// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+
+const mathf = require('./Math-Functions');
+const logger = require('./Logging');
+const { ComplexNumber } = require('./Q-ComplexNumber');
+const {Matrix} = require('./Q-Matrix');
+Gate = function( params ){
+
+ Object.assign( this, params )
+ this.index = Gate.index ++
+
+ if( typeof this.symbol !== 'string' ) this.symbol = '?'
+ const parameters = Object.assign( {}, params.parameters )
+ this.parameters = parameters
+
+ // We use symbols as unique identifiers
+ // among gate CONSTANTS
+ // so if you use the same symbol for a non-constant
+ // that’s not a deal breaker
+ // but it is good to know.
+
+ const
+ scope = this,
+ foundConstant = Object
+ .values( Gate.constants )
+ .find( function( gate ){
+
+ return gate.symbol === scope.symbol
+ })
+
+ if( foundConstant ){
+
+ logger.warn( `Gate is creating a new instance, #${ this.index }, that uses the same symbol as a pre-existing Gate constant:`, foundConstant )
+ }
+
+ if( typeof this.name !== 'string' ) this.name = 'Unknown'
+ if( typeof this.nameCss !== 'string' ) this.nameCss = 'unknown'
+
+
+ // If our gate’s matrix is to be
+ // dynamically created or updated
+ // then we ouoght to do that now.
+
+ if( typeof this.updateMatrix$ === 'function' ) this.updateMatrix$()
+
+
+ // Every gate must have an applyToQubit method.
+ // If it doesn’t exist we’ll create one
+ // based on whether a matrix property exists or not.
+
+ //Hi there. LTNLN here. We're just gonna toss the applyToQubit function entirely...Gate from here on is independent of Qubit! :)..
+}
+
+
+
+Object.assign( Gate, {
+
+ index: 0,
+ constants: {},
+ createConstant: function( key, value ){
+ this[ key ] = value
+ this.constants[ key ] = this[ key ]
+ Object.freeze( this[ key ])
+ },
+ createConstants: function(){
+
+ if( arguments.length % 2 !== 0 ){
+
+ return logger.error( 'Q attempted to create constants with invalid (KEY, VALUE) pairs.' )
+ }
+ for( let i = 0; i < arguments.length; i += 2 ){
+
+ this.createConstant( arguments[ i ], arguments[ i + 1 ])
+ }
+ },
+ findBy: function( key, value ){
+
+ return (
+
+ Object
+ .values( Gate.constants )
+ .find( function( item ){
+
+ if( typeof value === 'string' &&
+ typeof item[ key ] === 'string' ){
+
+ return value.toLowerCase() === item[ key ].toLowerCase()
+ }
+ return value === item[ key ]
+ })
+ )
+ },
+ findBySymbol: function( symbol ){
+
+ return Gate.findBy( 'symbol', symbol )
+ },
+ findByName: function( name ){
+
+ return Gate.findBy( 'name', name )
+ }
+})
+
+
+
+
+Object.assign( Gate.prototype, {
+
+ clone: function( params ){
+
+ return new Gate( Object.assign( {}, this, params ))
+ },
+ set$: function( key, value ){
+
+ this[ key ] = value
+ return this
+ },
+ setSymbol$: function( value ){
+
+ return this.set$( 'symbol', value )
+ }
+})
+
+
+
+
+Gate.createConstants (
// Operate on a single qubit.
- 'IDENTITY', new Q.Gate({
+ 'IDENTITY', new Gate({
symbol: 'I',
symbolAmazonBraket: 'i',
symbolSvg: '',
name: 'Identity',
nameCss: 'identity',
- matrix: Q.Matrix.IDENTITY_2X2
+ matrix: Matrix.IDENTITY_2X2
}),
- 'CURSOR', new Q.Gate({
+ 'CURSOR', new Gate({
symbol: '*',
symbolAmazonBraket: 'i',
symbolSvg: '',
name: 'Identity',
nameCss: 'identity',
- matrix: Q.Matrix.IDENTITY_2X2
+ matrix: Matrix.IDENTITY_2X2
}),
- 'MEASURE', new Q.Gate({
+ 'MEASURE', new Gate({
symbol: 'M',
symbolAmazonBraket: 'm',
symbolSvg: '',
name: 'Measure',
nameCss: 'measure',
- matrix: Q.Matrix.IDENTITY_2X2,
- applyToQubit: function( state ){}
+ matrix: Matrix.IDENTITY_2X2,
}),
- 'HADAMARD', new Q.Gate({
+ 'HADAMARD', new Gate({
symbol: 'H',
symbolAmazonBraket: 'h',
symbolSvg: '',
name: 'Hadamard',
nameCss: 'hadamard',
- matrix: new Q.Matrix(
+ matrix: new Matrix(
[ Math.SQRT1_2, Math.SQRT1_2 ],
[ Math.SQRT1_2, -Math.SQRT1_2 ])
}),
- 'PAULI_X', new Q.Gate({
+ 'PAULI_X', new Gate({
symbol: 'X',
symbolAmazonBraket: 'x',
symbolSvg: '',
name: 'Pauli X',
nameCss: 'pauli-x',
- matrix: new Q.Matrix(
+ matrix: new Matrix(
[ 0, 1 ],
[ 1, 0 ]),
//ltnln: NOTE! can_be_controlled refers to whether or not the Braket SDK supports a controlled
@@ -2572,33 +3626,33 @@ Q.Gate.createConstants(
can_be_controlled: true
},
),
- 'PAULI_Y', new Q.Gate({
+ 'PAULI_Y', new Gate({
symbol: 'Y',
symbolAmazonBraket: 'y',
symbolSvg: '',
name: 'Pauli Y',
nameCss: 'pauli-y',
- matrix: new Q.Matrix(
- [ 0, new Q.ComplexNumber( 0, -1 )],
- [ new Q.ComplexNumber( 0, 1 ), 0 ]),
+ matrix: new Matrix(
+ [ 0, new ComplexNumber( 0, -1 )],
+ [ new ComplexNumber( 0, 1 ), 0 ]),
can_be_controlled: true
},
),
- 'PAULI_Z', new Q.Gate({
+ 'PAULI_Z', new Gate({
symbol: 'Z',
symbolAmazonBraket: 'z',
symbolSvg: '',
name: 'Pauli Z',
nameCss: 'pauli-z',
- matrix: new Q.Matrix(
+ matrix: new Matrix(
[ 1, 0 ],
[ 0, -1 ]),
can_be_controlled: true
},
),
- 'PHASE', new Q.Gate({
+ 'PHASE', new Gate({
symbol: 'P',
symbolAmazonBraket: 'phaseshift',// ltnln edit: change from 'p' to 'phaseshift'
@@ -2607,47 +3661,39 @@ Q.Gate.createConstants(
nameCss: 'phase',
parameters: { "phi" : 1 },
updateMatrix$: function( phi ){
- if( Q.isUsefulNumber( phi ) === true ) this.parameters[ "phi" ] = phi
- this.matrix = new Q.Matrix(
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi;
+ this.matrix = new Matrix(
[ 1, 0 ],
- [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, this.parameters["phi"] ))])
+ [ 0, ComplexNumber.E.power( new ComplexNumber( 0, this.parameters[ "phi" ] ))])
return this
},
- applyToQubit: function( qubit, phi ){
-
- if( Q.isUsefulNumber( phi ) !== true ) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ 1, 0 ],
- [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, phi ))])
- return new Q.Qubit( matrix.multiply( qubit ))
- },
can_be_controlled: true,
has_parameters: true
}),
- 'PI_8', new Q.Gate({
+ 'PI_8', new Gate({
symbol: 'T',
symbolAmazonBraket: 't',// !!! Double check this !!!
symbolSvg: '',
name: 'π ÷ 8',
nameCss: 'pi8',
- matrix: new Q.Matrix(
+ matrix: new Matrix(
[ 1, 0 ],
- [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, Math.PI / 4 )) ])
+ [ 0, ComplexNumber.E.power( new ComplexNumber( 0, Math.PI / 4 )) ])
}),
- 'BLOCH', new Q.Gate({
+ 'BLOCH', new Gate({
symbol: 'B',
//symbolAmazonBraket: Does not exist.
symbolSvg: '',
name: 'Bloch sphere',
nameCss: 'bloch',
- applyToQubit: function( qubit ){
+ // applyToQubit: function( qubit ){
- // Create Bloch sphere visualizer instance.
- }
+ // // Create Bloch sphere visualizer instance.
+ // }
}),
- 'RX', new Q.Gate({
+ 'RX', new Gate({
symbol: 'Rx',
symbolAmazonBraket: 'rx',
@@ -2657,23 +3703,15 @@ Q.Gate.createConstants(
parameters: { "phi" : Math.PI / 2 },
updateMatrix$: function( phi ){
- if( Q.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
- this.matrix = new Q.Matrix(
- [ Math.cos( this.parameters[ "phi" ] / 2 ), new Q.ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )) ],
- [ new Q.ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), Math.cos( this.parameters[ "phi" ] / 2 )])
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
+ this.matrix = new Matrix(
+ [ Math.cos( this.parameters[ "phi" ] / 2 ), new ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )) ],
+ [ new ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), Math.cos( this.parameters[ "phi" ] / 2 )])
return this
},
- applyToQubit: function( qubit, phi ){
-
- if( Q.isUsefulNumber( phi ) !== true ) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ Math.cos( phi / 2 ), new Q.ComplexNumber( 0, -Math.sin( phi / 2 )) ],
- [ new Q.ComplexNumber( 0, -Math.sin( phi / 2 )), Math.cos( phi / 2 )])
- return new Q.Qubit( matrix.multiply( qubit ))
- },
has_parameters: true
}),
- 'RY', new Q.Gate({
+ 'RY', new Gate({
symbol: 'Ry',
symbolAmazonBraket: 'ry',
@@ -2683,23 +3721,15 @@ Q.Gate.createConstants(
parameters: { "phi" : Math.PI / 2 },
updateMatrix$: function( phi ){
- if( Q.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
- this.matrix = new Q.Matrix(
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
+ this.matrix = new Matrix(
[ Math.cos( this.parameters[ "phi" ] / 2 ), -Math.sin( phi / 2 ) ],
[ Math.sin( this.parameters[ "phi" ] / 2 ), Math.cos( this.parameters[ "phi" ] / 2 )])
return this
},
- applyToQubit: function( qubit, phi ){
-
- if( Q.isUsefulNumber( phi ) !== true ) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ Math.cos( phi / 2 ), -Math.sin( phi / 2 ) ],
- [ Math.sin( phi / 2 ), Math.cos( phi / 2 )])
- return new Q.Qubit( matrix.multiply( qubit ))
- },
has_parameters: true
}),
- 'RZ', new Q.Gate({
+ 'RZ', new Gate({
symbol: 'Rz',
symbolAmazonBraket: 'rz',
@@ -2709,23 +3739,15 @@ Q.Gate.createConstants(
parameters: { "phi" : Math.PI / 2 },
updateMatrix$: function( phi ){
- if( Q.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
- this.matrix = new Q.Matrix(
- [ Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, -this.parameters[ "phi" ] / 2 )), 0 ],
- [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, this.parameters[ "phi" ] / 2 ))])
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
+ this.matrix = new Matrix(
+ [ ComplexNumber.E.power( new ComplexNumber( 0, -this.parameters[ "phi" ] / 2 )), 0 ],
+ [ 0, ComplexNumber.E.power( new ComplexNumber( 0, this.parameters[ "phi" ] / 2 ))])
return this
},
- applyToQubit: function( qubit, phi ){
-
- if( Q.isUsefulNumber( phi ) !== true ) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, -phi / 2 )), 0 ],
- [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, phi / 2 ))])
- return new Q.Qubit( matrix.multiply( qubit ))
- },
has_parameters: true
}),
- 'UNITARY', new Q.Gate({
+ 'UNITARY', new Gate({
symbol: 'U',
symbolAmazonBraket: 'unitary',
@@ -2737,103 +3759,86 @@ Q.Gate.createConstants(
"theta" : Math.PI / 2,
"lambda" : Math.PI / 2 },
updateMatrix$: function( phi, theta, lambda ){
- //if all are valid, update; otherwise, update none.
- if( (Q.isUsefulNumber( +phi ) === true) && (Q.isUsefulNumber( +theta ) === true) && (Q.isUsefulNumber( +lambda ) === true) ) {
+
+ if( (mathf.isUsefulNumber( +phi ) === true) && (mathf.isUsefulNumber( +theta ) === true) && (mathf.isUsefulNumber( +lambda ) === true) ) {
this.parameters[ "phi" ] = +phi;
this.parameters[ "theta" ] = +theta;
this.parameters[ "lambda" ] = +lambda;
}
- const a = Q.ComplexNumber.multiply(
- Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, -( this.parameters[ "phi" ] + this.parameters[ "lambda" ] ) / 2 )), Math.cos( this.parameters[ "theta" ] / 2 ))
- const b = Q.ComplexNumber.multiply(
- Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, -( this.parameters[ "phi" ] - this.parameters[ "lambda" ] ) / 2 )), -Math.sin( this.parameters[ "theta" ] / 2 ))
- const c = Q.ComplexNumber.multiply(
- Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, ( this.parameters[ "phi" ] - this.parameters[ "lambda" ] ) / 2 )), Math.sin( this.parameters[ "theta" ] / 2 ))
- const d = Q.ComplexNumber.multiply(
- Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, ( this.parameters[ "phi" ] + this.parameters[ "lambda" ] ) / 2 )), Math.cos( this.parameters[ "theta" ] / 2 ))
- this.matrix = new Q.Matrix(
+ const a = ComplexNumber.multiply(
+ ComplexNumber.E.power( new ComplexNumber( 0, -( this.parameters[ "phi" ] + this.parameters[ "lambda" ] ) / 2 )), Math.cos( this.parameters[ "theta" ] / 2 ))
+ const b = ComplexNumber.multiply(
+ ComplexNumber.E.power( new ComplexNumber( 0, -( this.parameters[ "phi" ] - this.parameters[ "lambda" ] ) / 2 )), -Math.sin( this.parameters[ "theta" ] / 2 ))
+ const c = ComplexNumber.multiply(
+ ComplexNumber.E.power( new ComplexNumber( 0, ( this.parameters[ "phi" ] - this.parameters[ "lambda" ] ) / 2 )), Math.sin( this.parameters[ "theta" ] / 2 ))
+ const d = ComplexNumber.multiply(
+ ComplexNumber.E.power( new ComplexNumber( 0, ( this.parameters[ "phi" ] + this.parameters[ "lambda" ] ) / 2 )), Math.cos( this.parameters[ "theta" ] / 2 ))
+ this.matrix = new Matrix(
[ a, b ],
[ c, d ])
return this
},
- applyToQubit: function( qubit, phi, theta, lambda ){
- if( Q.isUsefulNumber( phi ) === true ) phi = this.parameters[ "phi" ]
- if( Q.isUsefulNumber( theta ) === true ) theta = this.parameters[ "theta" ]
- if( Q.isUsefulNumber( lambda ) === true ) lambda = this.parameters[ "lambda" ]
- const a = Q.ComplexNumber.multiply(
- Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, -( phi + lambda ) / 2 )), Math.cos( theta / 2 ));
- const b = Q.ComplexNumber.multiply(
- Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, -( phi - lambda ) / 2 )), -Math.sin( theta / 2 ));
- const c = Q.ComplexNumber.multiply(
- Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, ( phi - lambda ) / 2 )), Math.sin( theta / 2 ));
- const d = Q.ComplexNumber.multiply(
- Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, ( phi + lambda ) / 2 )), Math.cos( theta / 2 ));
- const matrix = new Q.Matrix(
- [ a, b ],
- [ c, d ])
- return new Q.Qubit( matrix.multiply( qubit ))
- },
has_parameters: true
}),
- 'NOT1_2', new Q.Gate({
+ 'NOT1_2', new Gate({
symbol: 'V',
symbolAmazonBraket: 'v',
symbolSvg: '',
name: '√Not',
nameCss: 'not1-2',
- matrix: new Q.Matrix(
- [ new Q.ComplexNumber( 1, 1 ) / 2, new Q.ComplexNumber( 1, -1 ) / 2 ],
- [ new Q.ComplexNumber( 1, -1 ) / 2, new Q.ComplexNumber( 1, 1 ) / 2 ])
+ matrix: new Matrix(
+ [ new ComplexNumber( 1, 1 ) / 2, new ComplexNumber( 1, -1 ) / 2 ],
+ [ new ComplexNumber( 1, -1 ) / 2, new ComplexNumber( 1, 1 ) / 2 ])
}),
- 'PI_8_Dagger', new Q.Gate({
+ 'PI_8_Dagger', new Gate({
symbol: 'T†',
symbolAmazonBraket: 'ti',
symbolSvg: '',
name: 'PI_8_Dagger',
nameCss: 'pi8-dagger',
- matrix: new Q.Matrix(
+ matrix: new Matrix(
[ 1, 0 ],
- [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, -Math.PI / 4 )) ])
+ [ 0, ComplexNumber.E.power( new ComplexNumber( 0, -Math.PI / 4 )) ])
}),
- 'NOT1_2_Dagger', new Q.Gate({
+ 'NOT1_2_Dagger', new Gate({
symbol: 'V†',
symbolAmazonBraket: 'vi',
symbolSvg: '',
name: '√Not_Dagger',
nameCss: 'not1-2-dagger',
- matrix: new Q.Matrix(
- [ new Q.ComplexNumber( 1, -1 ) / 2, new Q.ComplexNumber( 1, 1 ) / 2 ],
- [ new Q.ComplexNumber( 1, 1 ) / 2, new Q.ComplexNumber( 1, -1 ) / 2 ])
+ matrix: new Matrix(
+ [ new ComplexNumber( 1, -1 ) / 2, new ComplexNumber( 1, 1 ) / 2 ],
+ [ new ComplexNumber( 1, 1 ) / 2, new ComplexNumber( 1, -1 ) / 2 ])
}),
//Note that S, S_Dagger, PI_8, and PI_8_Dagger can all be implemented by applying the PHASE gate
//using certain values of phi.
//These gates are included for completeness.
- 'S', new Q.Gate({
+ 'S', new Gate({
symbol: 'S*', //Gotta think of a better symbol name...
symbolAmazonBraket: 's',
symbolSvg: '',
name: 'π ÷ 4',
nameCss: 'pi4',
- matrix: new Q.Matrix(
+ matrix: new Matrix(
[ 1, 0 ],
- [ 0, new Q.ComplexNumber( 0, 1 ) ])
+ [ 0, new ComplexNumber( 0, 1 ) ])
}),
- 'S_Dagger', new Q.Gate({
+ 'S_Dagger', new Gate({
symbol: 'S†',
symbolAmazonBraket: 'si',
symbolSvg: '',
name: 'π ÷ 4 Dagger',
nameCss: 'pi4-dagger',
- matrix: new Q.Matrix(
+ matrix: new Matrix(
[ 1, 0 ],
- [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, -1 )) ])
+ [ 0, ComplexNumber.E.power( new ComplexNumber( 0, -1 )) ])
}),
// Operate on 2 qubits.
- 'SWAP', new Q.Gate({
+ 'SWAP', new Gate({
symbol: 'S',
symbolAmazonBraket: 'swap',
@@ -2843,58 +3848,47 @@ Q.Gate.createConstants(
parameters: { "phi" : 0.0 },
updateMatrix$: function( phi ) {
- if( Q.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
- this.matrix = new Q.Matrix(
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
+ this.matrix = new Matrix(
[ 1, 0, 0, 0 ],
- [ 0, 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, this.parameters[ "phi" ] )), 0 ],
- [ 0, Q.ComplexNumber.E.power(new Q.ComplexNumber( 0, this.parameters[ "phi" ] )), 0, 0 ],
+ [ 0, 0, ComplexNumber.E.power( new ComplexNumber( 0, this.parameters[ "phi" ] )), 0 ],
+ [ 0, ComplexNumber.E.power(new ComplexNumber( 0, this.parameters[ "phi" ] )), 0, 0 ],
[ 0, 0, 0, 1 ])
return this
},
- applyToQubit: function( qubit, phi ) {
-
- if( Q.isUsefulNumber( phi ) !== true) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ 1, 0, 0, 0 ],
- [ 0, 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, phi )), 0 ],
- [ 0, new Q.ComplexNumber( 0, 1 ), 0, 0 ],
- [ 0, 0, 0, 1 ]
- )
- return new Q.Qubit( matrix.multiply( qubit ))
- },
can_be_controlled: true,
has_parameters: true,
is_multi_qubit: true
}),
- 'SWAP1_2', new Q.Gate({
+ 'SWAP1_2', new Gate({
symbol: '√S',
//symbolAmazonBraket: !!! UNKNOWN !!!
symbolSvg: '',
name: '√Swap',
nameCss: 'swap1-2',
- matrix: new Q.Matrix(
+ matrix: new Matrix(
[ 1, 0, 0, 0 ],
- [ 0, new Q.ComplexNumber( 0.5, 0.5 ), new Q.ComplexNumber( 0.5, -0.5 ), 0 ],
- [ 0, new Q.ComplexNumber( 0.5, -0.5 ), new Q.ComplexNumber( 0.5, 0.5 ), 0 ],
+ [ 0, new ComplexNumber( 0.5, 0.5 ), new ComplexNumber( 0.5, -0.5 ), 0 ],
+ [ 0, new ComplexNumber( 0.5, -0.5 ), new ComplexNumber( 0.5, 0.5 ), 0 ],
[ 0, 0, 0, 1 ]),
is_multi_qubit: true
}),
- 'ISWAP', new Q.Gate({
+ 'ISWAP', new Gate({
symbol: 'iS',
symbolAmazonBraket: 'iswap',
symbolSvg: '',
name: 'Imaginary Swap',
nameCss: 'iswap',
- matrix: new Q.Matrix(
+ matrix: new Matrix(
[ 1, 0, 0, 0 ],
- [ 0, 0, new Q.ComplexNumber( 0, 1 ), 0 ],
- [ 0, new Q.ComplexNumber( 0, 1 ), 0, 0 ],
+ [ 0, 0, new ComplexNumber( 0, 1 ), 0 ],
+ [ 0, new ComplexNumber( 0, 1 ), 0, 0 ],
[ 0, 0, 0, 1 ]),
is_multi_qubit: true
}),
- 'ISING-XX', new Q.Gate({
+ 'ISING-XX', new Gate({
symbol: 'XX',
symbolAmazonBraket: 'xx',
@@ -2904,28 +3898,18 @@ Q.Gate.createConstants(
parameters: { "phi" : 1 },
updateMatrix$: function( phi ) {
- if( Q.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
- this.matrix = new Q.Matrix(
- [ Math.cos( this.parameters[ "phi" ] / 2 ), 0, 0, new Q.ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )) ],
- [ 0, Math.cos( this.parameters[ "phi" ] / 2 ), new Q.ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), 0 ],
- [ 0, new Q.ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), Math.cos( this.parameters[ "phi" ] / 2 ), 0 ],
- [ new Q.ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), 0, 0, Math.cos( this.parameters[ "phi" ] / 2 ) ])
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
+ this.matrix = new Matrix(
+ [ Math.cos( this.parameters[ "phi" ] / 2 ), 0, 0, new ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )) ],
+ [ 0, Math.cos( this.parameters[ "phi" ] / 2 ), new ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), 0 ],
+ [ 0, new ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), Math.cos( this.parameters[ "phi" ] / 2 ), 0 ],
+ [ new ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), 0, 0, Math.cos( this.parameters[ "phi" ] / 2 ) ])
return this
},
- applyToQubit: function( qubit, phi ) {
- if( Q.isUsefulNumber( phi ) !== true) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ Math.cos( phi / 2 ), 0, 0, new Q.ComplexNumber( 0, -Math.sin( phi / 2 )) ],
- [ 0, Math.cos( phi / 2 ), new Q.ComplexNumber( 0, -Math.sin( phi / 2 )), 0 ],
- [ 0, new Q.ComplexNumber( 0, -Math.sin( phi / 2 )), Math.cos( phi / 2 ), 0 ],
- [ new Q.ComplexNumber( 0, -Math.sin( phi / 2 )), 0, 0, Math.cos( phi / 2 ) ]
- )
- return new Q.Qubit( matrix.multiply( qubit ))
- },
is_multi_qubit: true,
has_parameters: true
}),
- 'ISING-XY', new Q.Gate({
+ 'ISING-XY', new Gate({
symbol: 'XY',
symbolAmazonBraket: 'xy',
@@ -2935,28 +3919,18 @@ Q.Gate.createConstants(
parameters: { "phi" : 1 },
updateMatrix$: function( phi ) {
- if( Q.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
- this.matrix = new Q.Matrix(
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
+ this.matrix = new Matrix(
[ 1, 0, 0, 0 ],
- [ 0, Math.cos( this.parameters[ "phi" ] / 2 ), new Q.ComplexNumber( 0, Math.sin( this.parameters[ "phi" ] / 2 )), 0 ],
- [ 0, new Q.ComplexNumber( 0, Math.sin( this.parameters[ "phi" ] / 2 )), Math.cos( this.parameters[ "phi" ] / 2 ), 0 ],
+ [ 0, Math.cos( this.parameters[ "phi" ] / 2 ), new ComplexNumber( 0, Math.sin( this.parameters[ "phi" ] / 2 )), 0 ],
+ [ 0, new ComplexNumber( 0, Math.sin( this.parameters[ "phi" ] / 2 )), Math.cos( this.parameters[ "phi" ] / 2 ), 0 ],
[ 0, 0, 0, 1 ])
return this
},
- applyToQubit: function( qubit, phi ) {
- if( Q.isUsefulNumber( phi ) !== true) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ 1, 0, 0, 0 ],
- [ 0, Math.cos( phi / 2 ), new Q.ComplexNumber( 0, Math.sin( phi / 2 )), 0 ],
- [ 0, new Q.ComplexNumber( 0, Math.sin( phi / 2 )), Math.cos( phi / 2 ), 0 ],
- [ 0, 0, 0, 1 ]
- )
- return new Q.Qubit( matrix.multiply( qubit ))
- },
is_multi_qubit: true,
has_parameters: true
}),
- 'ISING-YY', new Q.Gate({
+ 'ISING-YY', new Gate({
symbol: 'YY',
symbolAmazonBraket: 'yy',
@@ -2966,28 +3940,18 @@ Q.Gate.createConstants(
parameters: { "phi" : 1 },
updateMatrix$: function( phi ) {
- if( Q.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
- this.matrix = new Q.Matrix(
- [ Math.cos( this.parameters[ "phi" ] / 2 ), 0, 0, new Q.ComplexNumber( 0, Math.sin( this.parameters[ "phi" ] / 2 )) ],
- [ 0, Math.cos( this.parameters[ "phi" ] / 2 ), new Q.ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), 0 ],
- [ 0, new Q.ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), Math.cos( this.parameters[ "phi" ] / 2 ), 0 ],
- [ new Q.ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), 0, 0, Math.cos( this.parameters[ "phi" ] / 2 ) ])
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
+ this.matrix = new Matrix(
+ [ Math.cos( this.parameters[ "phi" ] / 2 ), 0, 0, new ComplexNumber( 0, Math.sin( this.parameters[ "phi" ] / 2 )) ],
+ [ 0, Math.cos( this.parameters[ "phi" ] / 2 ), new ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), 0 ],
+ [ 0, new ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), Math.cos( this.parameters[ "phi" ] / 2 ), 0 ],
+ [ new ComplexNumber( 0, -Math.sin( this.parameters[ "phi" ] / 2 )), 0, 0, Math.cos( this.parameters[ "phi" ] / 2 ) ])
return this
},
- applyToQubit: function( qubit, phi ) {
- if( Q.isUsefulNumber( phi ) !== true) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ Math.cos( phi / 2 ), 0, 0, new Q.ComplexNumber( 0, -Math.sin( phi / 2 )) ],
- [ 0, Math.cos( phi / 2 ), new Q.ComplexNumber( 0, -Math.sin( phi / 2 )), 0 ],
- [ 0, new Q.ComplexNumber( 0, -Math.sin( phi / 2 )), Math.cos( phi / 2 ), 0 ],
- [ new Q.ComplexNumber( 0, Math.sin( phi / 2 )), 0, 0, Math.cos( phi / 2 ) ]
- )
- return new Q.Qubit( matrix.multiply( qubit ))
- },
is_multi_qubit: true,
has_parameters: true
}),
- 'ISING-ZZ', new Q.Gate({
+ 'ISING-ZZ', new Gate({
symbol: 'ZZ',
symbolAmazonBraket: 'zz',
@@ -2997,28 +3961,18 @@ Q.Gate.createConstants(
parameters: { "phi" : 1 },
updateMatrix$: function( phi ) {
- if( Q.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
- this.matrix = new Q.Matrix(
- [ Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, this.parameters[ "phi" ] / 2 )), 0, 0, 0 ],
- [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, this.parameters[ "phi" ] / 2 )), 0, 0 ],
- [ 0, 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, this.parameters[ "phi" ] / 2 )), 0],
- [ 0, 0, 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, -this.parameters[ "phi" ] / 2 )) ])
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
+ this.matrix = new Matrix(
+ [ ComplexNumber.E.power( new ComplexNumber( 0, this.parameters[ "phi" ] / 2 )), 0, 0, 0 ],
+ [ 0, ComplexNumber.E.power( new ComplexNumber( 0, this.parameters[ "phi" ] / 2 )), 0, 0 ],
+ [ 0, 0, ComplexNumber.E.power( new ComplexNumber( 0, this.parameters[ "phi" ] / 2 )), 0],
+ [ 0, 0, 0, ComplexNumber.E.power( new ComplexNumber( 0, -this.parameters[ "phi" ] / 2 )) ])
return this
},
- applyToQubit: function( qubit, phi ) {
- if( Q.isUsefulNumber( phi ) !== true) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, phi / 2 )), 0, 0, 0 ],
- [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, phi / 2 )), 0, 0 ],
- [ 0, 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, phi / 2 )), 0],
- [ 0, 0, 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, -phi / 2 )) ]
- )
- return new Q.Qubit( matrix.multiply( qubit ))
- },
is_multi_qubit: true,
has_parameters: true
}),
- 'CPhase00', new Q.Gate({
+ 'CPhase00', new Gate({
symbol: '00', //placeholder
symbolAmazonBraket: 'cphaseshift00',
@@ -3028,28 +3982,18 @@ Q.Gate.createConstants(
parameters: { "phi" : 1 },
updateMatrix$: function( phi ) {
- if( Q.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
- this.matrix = new Q.Matrix(
- [ Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, this.parameters[ "phi" ] )), 0, 0, 0 ],
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
+ this.matrix = new Matrix(
+ [ ComplexNumber.E.power( new ComplexNumber( 0, this.parameters[ "phi" ] )), 0, 0, 0 ],
[ 0, 1, 0, 0 ],
[ 0, 0, 1, 0 ],
[ 0, 0, 0, 1 ])
return this
},
- applyToQubit: function( qubit, phi ) {
- if( Q.isUsefulNumber( phi ) !== true) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, phi )), 0, 0, 0 ],
- [ 0, 1, 0, 0 ],
- [ 0, 0, 1, 0 ],
- [ 0, 0, 0, 1 ]
- )
- return new Q.Qubit( matrix.multiply( qubit ))
- },
is_multi_qubit: true,
has_parameters: true
}),
- 'CPhase01', new Q.Gate({
+ 'CPhase01', new Gate({
symbol: '01', //placeholder
symbolAmazonBraket: 'cphaseshift01',
@@ -3059,28 +4003,18 @@ Q.Gate.createConstants(
parameters: { "phi" : 1 },
updateMatrix$: function( phi ) {
- if( Q.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
- this.matrix = new Q.Matrix(
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
+ this.matrix = new Matrix(
[ 1, 0, 0, 0 ],
- [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, this.parameters[ "phi" ] )), 0, 0 ],
+ [ 0, ComplexNumber.E.power( new ComplexNumber( 0, this.parameters[ "phi" ] )), 0, 0 ],
[ 0, 0, 1, 0 ],
[ 0, 0, 0, 1 ])
return this
},
- applyToQubit: function( qubit, phi ) {
- if( Q.isUsefulNumber( phi ) !== true) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ 1, 0, 0, 0 ],
- [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, phi)), 0, 0 ],
- [ 0, 0, 1, 0 ],
- [ 0, 0, 0, 1 ]
- )
- return new Q.Qubit( matrix.multiply( qubit ))
- },
is_multi_qubit: true,
has_parameters: true
}),
- 'CPhase10', new Q.Gate({
+ 'CPhase10', new Gate({
symbol: '10', //placeholder
symbolAmazonBraket: 'cphaseshift10',
@@ -3090,35 +4024,25 @@ Q.Gate.createConstants(
parameters: { "phi" : 1 },
updateMatrix$: function( phi ) {
- if( Q.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
- this.matrix = new Q.Matrix(
+ if( mathf.isUsefulNumber( +phi ) === true ) this.parameters[ "phi" ] = +phi
+ this.matrix = new Matrix(
[ 1, 0, 0, 0 ],
[ 0, 1, 0, 0 ],
- [ 0, 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, this.parameters[ "phi" ] )), 0 ],
+ [ 0, 0, ComplexNumber.E.power( new ComplexNumber( 0, this.parameters[ "phi" ] )), 0 ],
[ 0, 0, 0, 1 ])
return this
},
- applyToQubit: function( qubit, phi ) {
- if( Q.isUsefulNumber( phi ) !== true) phi = this.parameters[ "phi" ]
- const matrix = new Q.Matrix(
- [ 1, 0, 0, 0 ],
- [ 0, 1, 0, 0 ],
- [ 0, 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, phi)), 0 ],
- [ 0, 0, 0, 1 ]
- )
- return new Q.Qubit( matrix.multiply( qubit ))
- },
is_multi_qubit: true,
has_parameters: true
}),
- 'CSWAP', new Q.Gate({
+ 'CSWAP', new Gate({
symbol: 'CSWAP',
symbolAmazonBraket: 'cswap',
symbolSvg: '',
name: 'Controlled Swap',
nameCss: 'controlled-swap',
- matrix: new Q.Matrix(
+ matrix: new Matrix(
[1, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
@@ -3145,15 +4069,15 @@ Q.Gate.createConstants(
+module.exports = { Gate };
+},{"./Logging":3,"./Math-Functions":4,"./Q-ComplexNumber":7,"./Q-Matrix":10}],9:[function(require,module,exports){
+// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
-
-// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
-
-
+const {dispatchCustomEventToGlobal} = require('./Misc');
-Q.History = function( instance ){
+History = function( instance ){
this.instance = instance
this.entries = [[{
@@ -3168,38 +4092,34 @@ Q.History = function( instance ){
-Object.assign( Q.History.prototype, {
+Object.assign( History.prototype, {
assess: function(){
const instance = this.instance
if( this.index > 0 ){
- window.dispatchEvent( new CustomEvent(
-
- 'Q.History undo is capable', { detail: { instance }}
- ))
+ dispatchCustomEventToGlobal(
+ 'History undo is capable', { detail: { instance }}
+ );
}
else {
- window.dispatchEvent( new CustomEvent(
-
- 'Q.History undo is depleted', { detail: { instance }}
- ))
+ dispatchCustomEventToGlobal(
+ 'History undo is depleted', { detail: { instance }}
+ )
}
if( this.index + 1 < this.entries.length ){
- window.dispatchEvent( new CustomEvent(
-
- 'Q.History redo is capable', { detail: { instance }}
- ))
+ dispatchCustomEventToGlobal(
+ 'History redo is capable', { detail: { instance }}
+ )
}
else {
- window.dispatchEvent( new CustomEvent(
-
- 'Q.History redo is depleted', { detail: { instance }}
- ))
+ dispatchCustomEventToGlobal(
+ 'History redo is depleted', { detail: { instance }}
+ )
}
return this
},
@@ -3214,7 +4134,7 @@ Object.assign( Q.History.prototype, {
// Are we recording this history?
// Usually, yes.
- // But if our history state is “playbackâ€
+ // But if our history state is “playback”
// then we will NOT record this.
if( this.isRecording ){
@@ -3288,2203 +4208,1673 @@ Object.assign( Q.History.prototype, {
// then we decrement the history index
// AFTER the execution above.
- if( direction < 0 ) this.index --
-
-
- // It’s now safe to turn recording back on.
-
- this.isRecording = true
-
-
- // Emit an event so the GUI or anyone else listening
- // can know if we have available undo or redo commands
- // based on where or index is.
-
- this.assess()
- return true
- },
- undo$: function(){ return this.step$( -1 )},
- redo$: function(){ return this.step$( 1 )},
- report: function(){
-
- const argsParse = function( output, entry, i ){
-
- if( i > 0 ) output += ', '
- return output + ( typeof entry === 'object' && entry.name ? entry.name : entry )
- }
- return this.entries.reduce( function( output, entry, i ){
-
- output += '\n\n'+ i + ' â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•'+
- entry.reduce( function( output, entry, i ){
-
- output += '\n\n '+ i +' ────────────────────────────────────────\n'
- if( entry.redo ){
-
- output += '\n ⟳ Redo ── '+ entry.redo.name +' '
- if( entry.redo.args ) output += entry.redo.args.reduce( argsParse, '' )
- }
- output += entry.undo.reduce( function( output, entry, i ){
-
- output += '\n ⟲ Undo '+ i +' ── '+ entry.name +' '
- if( entry.args ) output += entry.args.reduce( argsParse, '' )
- return output
-
- }, '' )
-
- return output
-
- }, '' )
- return output
-
- }, 'History entry cursor: '+ this.index )
- }
-})
-
-
-
-
-// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
-
-
-
-
-Q.Circuit = function( bandwidth, timewidth ){
-
-
- // What number Circuit is this
- // that we’re attempting to make here?
-
- this.index = Q.Circuit.index ++
-
-
- // How many qubits (registers) shall we use?
-
- if( !Q.isUsefulInteger( bandwidth )) bandwidth = 3
- this.bandwidth = bandwidth
-
-
- // How many operations can we perform on each qubit?
- // Each operation counts as one moment; one clock tick.
-
- if( !Q.isUsefulInteger( timewidth )) timewidth = 5
- this.timewidth = timewidth
-
-
- // We’ll start with Horizontal qubits (zeros) as inputs
- // but we can of course modify this after initialization.
-
- this.qubits = new Array( bandwidth ).fill( Q.Qubit.HORIZONTAL )
-
-
- // What operations will we perform on our qubits?
-
- this.operations = []
-
-
- // Does our circuit need evaluation?
- // Certainly, yes!
- // (And will again each time it is modified.)
-
- this.needsEvaluation = true
-
-
- // When our circuit is evaluated
- // we store those results in this array.
-
- this.results = []
- this.matrix = null
-
-
- // Undo / Redo history.
-
- this.history = new Q.History( this )
-}
-
-
-
-
-Object.assign( Q.Circuit, {
-
- index: 0,
- help: function(){ return Q.help( this )},
- constants: {},
- createConstant: Q.createConstant,
- createConstants: Q.createConstants,
-
-
- fromText: function( text ){
-
-
- // This is a quick way to enable `fromText()`
- // to return a default new Q.Circuit().
-
- if( text === undefined ) return new Q.Circuit()
-
- // Is this a String Template -- as opposed to a regular String?
- // If so, let’s convert it to a regular String.
- // Yes, this maintains the line breaks.
-
- if( text.raw !== undefined ) text = ''+text.raw
- return Q.Circuit.fromTableTransposed(
-
- text
- .trim()
- .split( /\r?\n/ )
- .filter( function( item ){ return item.length })
- .map( function( item, r ){
-
- return item
- .trim()
- .split( /[-+\s+=+]/ )
- .filter( function( item ){ return item.length })
- .map( function( item, m ){
-
- //const matches = item.match( /(^\w+)(#(\w+))*(\.(\d+))*/ )
- const matches = item.match( /(^\w+)(\.(\w+))*(#(\d+))*/ )
- return {
-
- gateSymbol: matches[ 1 ],
- operationMomentId: matches[ 3 ],
- mappingIndex: +matches[ 5 ]
- }
- })
- })
- )
- },
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-// Working out a new syntax here... Patience please!
-
-
- fromText2: function( text ){
-
-
- text = `
- H C C
- I C1 C1
- I X1 S1
- I X1 S1`
-
-
- // This is a quick way to enable `fromText()`
- // to return a default new Q.Circuit().
-
- if( text === undefined ) return new Q.Circuit()
-
-
- // Is this a String Template -- as opposed to a regular String?
- // If so, let’s convert it to a regular String.
- // Yes, this maintains the line breaks.
-
- if( text.raw !== undefined ) text = ''+text.raw
-
-
-
- text
- .trim()
- .split( /\r?\n/ )
- .filter( function( item ){ return item.length })
- .map( function( item, r ){
-
- return item
- .trim()
- .split( /[-+\s+=+]/ )
- .filter( function( item ){ return item.length })
- .map( function( item, m ){
-
- // +++++++++++++++++++++++
- // need to map LETTER[] optional NUMBER ]
-
- const matches = item.match( /(^\w+)(\.(\w+))*(#(\d+))*/ )
-
- //const matches = item.match( /(^\w+)(#(\w+))*(\.(\d+))*/ )
- // const matches = item.match( /(^\w+)(\.(\w+))*(#(\d+))*/ )
- // return {
-
- // gateSymbol: matches[ 1 ],
- // operationMomentId: matches[ 3 ],
- // mappingIndex: +matches[ 5 ]
- // }
- })
- })
-
- },
-
-
-
-//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-
-
-
-
-
-
-
-
-
-
- fromTableTransposed: function( table ){
- const
- bandwidth = table.length,
- timewidth = table.reduce( function( max, moments ){
-
- return Math.max( max, moments.length )
-
- }, 0 ),
- circuit = new Q.Circuit( bandwidth, timewidth )
-
- circuit.bandwidth = bandwidth
- circuit.timewidth = timewidth
- for( let r = 0; r < bandwidth; r ++ ){
-
- const registerIndex = r + 1
- for( let m = 0; m < timewidth; m ++ ){
-
- const
- momentIndex = m + 1,
- operation = table[ r ][ m ]
- let siblingHasBeenFound = false
- for( let s = 0; s < r; s ++ ){
-
- const sibling = table[ s ][ m ]
- if( operation.gateSymbol === sibling.gateSymbol &&
- operation.operationMomentId === sibling.operationMomentId &&
- Q.isUsefulInteger( operation.mappingIndex ) &&
- Q.isUsefulInteger( sibling.mappingIndex ) &&
- operation.mappingIndex !== sibling.mappingIndex ){
-
-
- // We’ve found a sibling !
- const operationsIndex = circuit.operations.findIndex( function( operation ){
-
- return (
-
- operation.momentIndex === momentIndex &&
- operation.registerIndices.includes( s + 1 )
- )
- })
- // console.log( 'operationsIndex?', operationsIndex )
- circuit.operations[ operationsIndex ].registerIndices[ operation.mappingIndex ] = registerIndex
- circuit.operations[ operationsIndex ].isControlled = operation.gateSymbol != '*'// Q.Gate.SWAP.
- siblingHasBeenFound = true
- }
- }
- if( siblingHasBeenFound === false && operation.gateSymbol !== 'I' ){
-
- const
- gate = Q.Gate.findBySymbol( operation.gateSymbol ),
- registerIndices = []
-
- if( Q.isUsefulInteger( operation.mappingIndex )){
-
- registerIndices[ operation.mappingIndex ] = registerIndex
- }
- else registerIndices[ 0 ] = registerIndex
- circuit.operations.push({
-
- gate,
- momentIndex,
- registerIndices,
- isControlled: false,
- operationMomentId: operation.operationMomentId
- })
- }
- }
- }
- circuit.sort$()
- return circuit
- },
-
-
-
-
- controlled: function( U ){
-
-
- // we should really just replace this with a nice Matrix.copy({}) command!!!!
-
- // console.log( 'U?', U )
-
- const
- size = U.getWidth(),
- result = Q.Matrix.createIdentity( size * 2 )
-
- // console.log( 'U', U.toTsv() )
- // console.log( 'size', size )
- // console.log( 'result', result.toTsv() )
-
- for( let x = 0; x < size; x ++ ){
-
- for( let y = 0; y < size; y ++ ){
-
- const v = U.read( x, y )
- // console.log( `value at ${x}, ${y}`, v )
- result.write$( x + size, y + size, v )
- }
- }
- return result
- },
-
-
-
- // Return transformation over entire nqubit register that applies U to
- // specified qubits (in order given).
- // Algorithm from Lee Spector's "Automatic Quantum Computer Programming"
- // Page 21 in the 2004 PDF?
- // http://148.206.53.84/tesiuami/S_pdfs/AUTOMATIC%20QUANTUM%20COMPUTER%20PROGRAMMING.pdf
-
- expandMatrix: function( circuitBandwidth, U, qubitIndices ){
-
- // console.log( 'EXPANDING THE MATRIX...' )
- // console.log( 'this one: U', U.toTsv())
-
- const _qubits = []
- const n = Math.pow( 2, circuitBandwidth )
-
-
- // console.log( 'qubitIndices used by this operation:', qubitIndices )
- // console.log( 'qubits before slice', qubitIndices )
- // qubitIndices = qubitIndices.slice( 0 )
- // console.log( 'qubits AFTER slice', qubitIndices )
-
-
-
-
- for( let i = 0; i < qubitIndices.length; i ++ ){
-
- //qubitIndices[ i ] = ( circuitBandwidth - 1 ) - qubitIndices[ i ]
- qubitIndices[ i ] = ( circuitBandwidth - 0 ) - qubitIndices[ i ]
- }
- // console.log( 'qubits AFTER manipulation', qubitIndices )
-
-
- qubitIndices.reverse()
- for( let i = 0; i < circuitBandwidth; i ++ ){
-
- if( qubitIndices.indexOf( i ) == -1 ){
-
- _qubits.push( i )
- }
- }
-
-
- // console.log( 'qubitIndices vs _qubits:' )
- // console.log( 'qubitIndices', qubitIndices )
- // console.log( '_qubits', _qubits )
-
-
-
- const result = new Q.Matrix.createZero( n )
-
-
- // const X = numeric.rep([n, n], 0);
- // const Y = numeric.rep([n, n], 0);
-
-
- let i = n
- while( i -- ){
-
- let j = n
- while( j -- ){
-
- let
- bitsEqual = true,
- k = _qubits.length
-
- while( k -- ){
-
- if(( i & ( 1 << _qubits[ k ])) != ( j & ( 1 << _qubits[ k ]))){
-
- bitsEqual = false
- break
- }
- }
- if( bitsEqual ){
-
- // console.log( 'bits ARE equal' )
- let
- istar = 0,
- jstar = 0,
- k = qubitIndices.length
-
- while( k -- ){
-
- const q = qubitIndices[ k ]
- istar |= (( i & ( 1 << q )) >> q ) << k
- jstar |= (( j & ( 1 << q )) >> q ) << k
- }
- //console.log( 'U.read( istar, jstar )', U.read( istar, jstar ).toText() )
-
- // console.log( 'before write$', result.toTsv())
-
- // console.log( 'U.read at ', istar, jstar, '=', U.read( istar, jstar ).toText())
- result.write$( i, j, U.read( istar, jstar ))
-
- // console.log( 'after write$', result.toTsv())
-
- // X[i][j] = U.x[ istar ][ jstar ]
- // Y[i][j] = U.y[ istar ][ jstar ]
- }
- // else console.log('bits NOT equal')
- }
- }
- //return new numeric.T(X, Y);
-
- // console.log( 'expanded matrix to:', result.toTsv() )
- return result
- },
-
-
-
-
- evaluate: function( circuit ){
-
-
- // console.log( circuit.toDiagram() )
-
-
- window.dispatchEvent( new CustomEvent(
-
- 'Q.Circuit.evaluate began', {
-
- detail: { circuit }
- }
- ))
-
-
- // Our circuit’s operations must be in the correct order
- // before we attempt to step through them!
-
- circuit.sort$()
-
-
-
- // Create a new matrix (or more precisely, a vector)
- // that is a 1 followed by all zeros.
- //
- // ┌ ┐
- // │ 1 │
- // │ 0 │
- // │ 0 │
- // │ . │
- // │ . │
- // │ . │
- // └ ┘
-
- const state = new Q.Matrix( 1, Math.pow( 2, circuit.bandwidth ))
- state.write$( 0, 0, 1 )
-
-
-
-
- // Create a state matrix from this circuit’s input qubits.
-
- // const state2 = circuit.qubits.reduce( function( state, qubit, i ){
-
- // if( i > 0 ) return state.multiplyTensor( qubit )
- // else return state
-
- // }, circuit.qubits[ 0 ])
- // console.log( 'Initial state', state2.toTsv() )
- // console.log( 'multiplied', state2.multiplyTensor( state ).toTsv() )
-
-
-
-
-
- const operationsTotal = circuit.operations.length
- let operationsCompleted = 0
- let matrix = circuit.operations.reduce( function( state, operation, i ){
-
-
-
- let U
- if( operation.registerIndices.length < Infinity ){
-
- if( operation.isControlled ){
- //if( operation.registerIndices.length > 1 ){
-
- // operation.gate = Q.Gate.PAULI_X
- // why the F was this hardcoded in there?? what was i thinking?!
- // OH I KNOW !
- // that was from back when i represented this as "C" -- its own gate
- // rather than an X with multiple registers.
- // so now no need for this "if" block at all.
- // will remove in a few cycles.
- }
- U = operation.gate.matrix
- }
- else {
-
- // This is for Quantum Fourier Transforms (QFT).
- // Will have to come back to this at a later date!
- }
- // console.log( operation.gate.name, U.toTsv() )
-
-
-
-
-
- // Yikes. May need to separate registerIndices in to controls[] and targets[] ??
- // Works for now tho.....
- // Houston we have a problem. Turns out, not every gate with registerIndices.length > 1 is
- // controlled.
- // This is a nasty fix, leads to a lot of edge cases. (For instance: hard-coding cswaps...) But just experimenting.
- if(!operation.gate.is_multi_qubit || (operation.gate.symbol == 'S' && operation.registerIndices.length > 2) && operation.gate.can_be_controlled) {
- for( let j = 0; j < operation.registerIndices.length - 1; j ++ ){
-
- U = Q.Circuit.controlled( U )
- //console.log( 'qubitIndex #', j, 'U = Q.Circuit.controlled( U )', U.toTsv() )
- }
- }
-
-
- // We need to send a COPY of the registerIndices Array
- // to .expandMatrix()
- // otherwise it *may* modify the actual registerIndices Array
- // and wow -- tracking down that bug was painful!
-
- const registerIndices = operation.registerIndices.slice()
- state = Q.Circuit.expandMatrix(
-
- circuit.bandwidth,
- U,
- registerIndices
-
- ).multiply( state )
-
-
-
- operationsCompleted ++
- const progress = operationsCompleted / operationsTotal
-
-
- window.dispatchEvent( new CustomEvent( 'Q.Circuit.evaluate progressed', { detail: {
-
- circuit,
- progress,
- operationsCompleted,
- operationsTotal,
- momentIndex: operation.momentIndex,
- registerIndices: operation.registerIndices,
- gate: operation.gate.name,
- state
-
- }}))
-
-
- // console.log( `\n\nProgress ... ${ Math.round( operationsCompleted / operationsTotal * 100 )}%`)
- // console.log( 'Moment .....', operation.momentIndex )
- // console.log( 'Registers ..', JSON.stringify( operation.registerIndices ))
- // console.log( 'Gate .......', operation.gate.name )
- // console.log( 'Intermediate result:', state.toTsv() )
- // console.log( '\n' )
-
-
- return state
-
- }, state )
-
-
- // console.log( 'result matrix', matrix.toTsv() )
-
-
-
-
- const outcomes = matrix.rows.reduce( function( outcomes, row, i ){
-
- outcomes.push({
-
- state: '|'+ parseInt( i, 10 ).toString( 2 ).padStart( circuit.bandwidth, '0' ) +'⟩',
- probability: Math.pow( row[ 0 ].absolute(), 2 )
- })
- return outcomes
-
- }, [] )
-
-
-
- circuit.needsEvaluation = false
- circuit.matrix = matrix
- circuit.results = outcomes
-
-
-
- window.dispatchEvent( new CustomEvent( 'Q.Circuit.evaluate completed', { detail: {
- // circuit.dispatchEvent( new CustomEvent( 'evaluation complete', { detail: {
-
- circuit,
- results: outcomes
-
- }}))
-
-
-
-
- return matrix
- }
-})
-
-
-
-
-
-
-
-Object.assign( Q.Circuit.prototype, {
-
- clone: function(){
-
- const
- original = this,
- clone = original.copy()
-
- clone.qubits = original.qubits.slice()
- clone.results = original.results.slice()
- clone.needsEvaluation = original.needsEvaluation
-
- return clone
- },
- evaluate$: function(){
-
- Q.Circuit.evaluate( this )
- return this
- },
- report$: function( length ){
-
- if( this.needsEvaluation ) this.evaluate$()
- if( !Q.isUsefulInteger( length )) length = 20
-
- const
- circuit = this,
- text = this.results.reduce( function( text, outcome, i ){
-
- const
- probabilityPositive = Math.round( outcome.probability * length ),
- probabilityNegative = length - probabilityPositive
-
- return text +'\n'
- + ( i + 1 ).toString().padStart( Math.ceil( Math.log10( Math.pow( 2, circuit.qubits.length ))), ' ' ) +' '
- + outcome.state +' '
- + ''.padStart( probabilityPositive, '█' )
- + ''.padStart( probabilityNegative, '░' )
- + Q.round( Math.round( 100 * outcome.probability ), 8 ).toString().padStart( 4, ' ' ) +'% chance'
-
- }, '' ) + '\n'
- return text
- },
- try$: function(){
-
- if( this.needsEvaluation ) this.evaluate$()
-
-
- // We need to “stack” our probabilities from 0..1.
-
- const outcomesStacked = new Array( this.results.length )
- this.results.reduce( function( sum, outcome, i ){
-
- sum += outcome.probability
- outcomesStacked[ i ] = sum
- return sum
-
- }, 0 )
-
-
- // Now we can pick a random number
- // and return the first outcome
- // with a probability equal to or greater than
- // that random number.
-
- const
- randomNumber = Math.random(),
- randomIndex = outcomesStacked.findIndex( function( index ){
-
- return randomNumber <= index
- })
-
-
- // Output that to the console
- // but return the random index
- // so we can pipe that to something else
- // should we want to :)
-
- // console.log( this.outcomes[ randomIndex ].state )
- return randomIndex
- },
-
-
-
-
- ////////////////
- // //
- // Output //
- // //
- ////////////////
-
-
- // This is absolutely required by toTable.
-
- sort$: function(){
-
-
- // Sort this circuit’s operations
- // primarily by momentIndex,
- // then by the first registerIndex.
-
- this.operations.sort( function( a, b ){
-
- if( a.momentIndex === b.momentIndex ){
-
-
- // Note that we are NOT sorting registerIndices here!
- // We are merely asking which set of indices contain
- // the lowest register index.
- // If we instead sorted the registerIndices
- // we could confuse which qubit is the controller
- // and which is the controlled!
-
- return Math.min( ...a.registerIndices ) - Math.min( b.registerIndices )
- }
- else {
-
- return a.momentIndex - b.momentIndex
- }
- })
- return this
- },
-
-
-
-
-
-
- ///////////////////
- // //
- // Exporters //
- // //
- ///////////////////
-
-
- // Many export functions rely on toTable
- // and toTable itself absolutely relies on
- // a circuit’s operations to be SORTED correctly.
- // We could force circuit.sort$() here,
- // but then toTable would become toTable$
- // and every exporter that relies on it would
- // also become destructive.
-
- toTable: function(){
-
- const
- table = new Array( this.timewidth ),
- circuit = this
-
-
- // Sure, this is equal to table.length
- // but isn’t legibility and convenience everything?
-
- table.timewidth = this.timewidth
-
-
- // Similarly, this should be equal to table[ 0 ].length
- // or really table[ i >= 0; i < table.length ].length,
- // but again, lowest cognitive hurdle is key ;)
-
- table.bandwidth = this.bandwidth
-
-
- // First, let’s establish a “blank” table
- // that contains an identity operation
- // for each register during each moment.
-
- table.fill( 0 ).forEach( function( element, index, array ){
-
- const operations = new Array( circuit.bandwidth )
- operations.fill( 0 ).forEach( function( element, index, array ){
-
- array[ index ] = {
-
- symbol: 'I',
- symbolDisplay: 'I',
- name: 'Identity',
- nameCss: 'identity',
- gateInputIndex: 0,
- bandwidth: 0,
- thisGateAmongMultiQubitGatesIndex: 0,
- aSiblingIsAbove: false,
- aSiblingIsBelow: false
- }
- })
- array[ index ] = operations
- })
-
-
- // Now iterate through the circuit’s operations list
- // and note those operations in our table.
- // NOTE: This relies on operations being pre-sorted with .sort$()
- // prior to the .toTable() call.
-
- let
- momentIndex = 1,
- multiRegisterOperationIndex = 0,
- gateTypesUsedThisMoment = {}
-
- this.operations.forEach( function( operation, operationIndex, operations ){
-
-
- // We need to keep track of
- // how many multi-register operations
- // occur during this moment.
-
- if( momentIndex !== operation.momentIndex ){
-
- table[ momentIndex ].gateTypesUsedThisMoment = gateTypesUsedThisMoment
- momentIndex = operation.momentIndex
- multiRegisterOperationIndex = 0
- gateTypesUsedThisMoment = {}
- }
- if( operation.registerIndices.length > 1 ){
-
- table[ momentIndex - 1 ].multiRegisterOperationIndex = multiRegisterOperationIndex
- multiRegisterOperationIndex ++
- }
- if( gateTypesUsedThisMoment[ operation.gate.symbol ] === undefined ){
-
- gateTypesUsedThisMoment[ operation.gate.symbol ] = 1
- }
- else gateTypesUsedThisMoment[ operation.gate.symbol ] ++
-
-
- // By default, an operation’s CSS name
- // is its regular name, all lowercase,
- // with all spaces replaced by hyphens.
-
- let nameCss = operation.gate.name.toLowerCase().replace( /\s+/g, '-' )
-
-
- operation.registerIndices.forEach( function( registerIndex, indexAmongSiblings ){
-
- let isMultiRegisterOperation = false
- if( operation.registerIndices.length > 1 ){
-
- isMultiRegisterOperation = true
- if( indexAmongSiblings === operation.registerIndices.length - 1 ){
-
- nameCss = 'target'
- }
- else {
-
- nameCss = 'control'
- }
-
- // May need to re-visit the code above in consideration of SWAPs.
-
- }
- table[ operation.momentIndex - 1 ][ registerIndex - 1 ] = {
-
- symbol: operation.gate.symbol,
- symbolDisplay: operation.gate.symbol,
- name: operation.gate.name,
- nameCss,
- operationIndex,
- momentIndex: operation.momentIndex,
- registerIndex,
- isMultiRegisterOperation,
- multiRegisterOperationIndex,
- gatesOfThisTypeNow: gateTypesUsedThisMoment[ operation.gate.symbol ],
- indexAmongSiblings,
- siblingExistsAbove: Math.min( ...operation.registerIndices ) < registerIndex,
- siblingExistsBelow: Math.max( ...operation.registerIndices ) > registerIndex
- }
- })
-
-/*
-
-
-++++++++++++++++++++++
-
-Non-fatal problem to solve here:
-
-Previously we were concerned with “gates of this type used this moment”
-when we were thinking about CNOT as its own special gate.
-But now that we treat CNOT as just connected X gates,
-we now have situations
-where a moment can have one “CNOT” but also a stand-alone X gate
-and toTable will symbol the “CNOT” as X.0
-(never X.1, because it’s the only multi-register gate that moment)
-but still uses the symbol X.0 instead of just X
-because there’s another stand-alone X there tripping the logic!!!
-
-
-
-
-
-*/
-
-
- // if( operationIndex === operations.length - 1 ){
-
- table[ momentIndex - 1 ].gateTypesUsedThisMoment = gateTypesUsedThisMoment
- // }
- })
-
-
-
-
-
-
-
-
-
-
-
- table.forEach( function( moment, m ){
-
- moment.forEach( function( operation, o ){
-
- if( operation.isMultiRegisterOperation ){
-
- if( moment.gateTypesUsedThisMoment[ operation.symbol ] > 1 ){
-
- operation.symbolDisplay = operation.symbol +'.'+ ( operation.gatesOfThisTypeNow - 1 )
- }
- operation.symbolDisplay += '#'+ operation.indexAmongSiblings
- }
- })
- })
-
-
- // Now we can easily read down each moment
- // and establish the moment’s character width.
- // Very useful for text-based diagrams ;)
-
- table.forEach( function( moment ){
-
- const maximumWidth = moment.reduce( function( maximumWidth, operation ){
-
- return Math.max( maximumWidth, operation.symbolDisplay.length )
-
- }, 1 )
- moment.maximumCharacterWidth = maximumWidth
- })
-
-
- // We can also do this for the table as a whole.
-
- table.maximumCharacterWidth = table.reduce( function( maximumWidth, moment ){
-
- return Math.max( maximumWidth, moment.maximumCharacterWidth )
-
- }, 1 )
-
-
- // I think we’re done here.
-
- return table
- },
- toText: function( makeAllMomentsEqualWidth ){
-
- `
- Create a text representation of this circuit
- using only common characters,
- ie. no fancy box-drawing characters.
- This is the complement of Circuit.fromText()
- `
-
- const
- table = this.toTable(),
- output = new Array( table.bandwidth ).fill( '' )
-
- for( let x = 0; x < table.timewidth; x ++ ){
-
- for( let y = 0; y < table.bandwidth; y ++ ){
-
- let cellString = table[ x ][ y ].symbolDisplay.padEnd( table[ x ].maximumCharacterWidth, '-' )
- if( makeAllMomentsEqualWidth && x < table.timewidth - 1 ){
-
- cellString = table[ x ][ y ].symbolDisplay.padEnd( table.maximumCharacterWidth, '-' )
- }
- if( x > 0 ) cellString = '-'+ cellString
- output[ y ] += cellString
- }
- }
- return '\n'+ output.join( '\n' )
- // return output.join( '\n' )
- },
- toDiagram: function( makeAllMomentsEqualWidth ){
-
- `
- Create a text representation of this circuit
- using fancy box-drawing characters.
- `
-
- const
- scope = this,
- table = this.toTable(),
- output = new Array( table.bandwidth * 3 + 1 ).fill( '' )
-
- output[ 0 ] = ' '
- scope.qubits.forEach( function( qubit, q ){
-
- const y3 = q * 3
- output[ y3 + 1 ] += ' '
- output[ y3 + 2 ] += 'r'+ ( q + 1 ) +' |'+ qubit.beta.toText().trim() +'⟩─'
- output[ y3 + 3 ] += ' '
- })
- for( let x = 0; x < table.timewidth; x ++ ){
-
- const padToLength = makeAllMomentsEqualWidth
- ? table.maximumCharacterWidth
- : table[ x ].maximumCharacterWidth
-
- output[ 0 ] += Q.centerText( 'm'+ ( x + 1 ), padToLength + 4 )
- for( let y = 0; y < table.bandwidth; y ++ ){
-
- let
- operation = table[ x ][ y ],
- first = '',
- second = '',
- third = ''
-
- if( operation.symbol === 'I' ){
-
- first += ' '
- second += '──'
- third += ' '
-
- first += ' '.padEnd( padToLength )
- second += Q.centerText( '○', padToLength, '─' )
- third += ' '.padEnd( padToLength )
-
- first += ' '
- if( x < table.timewidth - 1 ) second += '──'
- else second += ' '
- third += ' '
- }
- else {
-
- if( operation.isMultiRegisterOperation ){
-
- first += '╭─'
- third += '╰─'
- }
- else {
-
- first += '┌─'
- third += '└─'
- }
- second += '┤ '
-
- first += '─'.padEnd( padToLength, '─' )
- second += Q.centerText( operation.symbolDisplay, padToLength )
- third += '─'.padEnd( padToLength, '─' )
-
-
- if( operation.isMultiRegisterOperation ){
-
- first += '─╮'
- third += '─╯'
- }
- else {
-
- first += '─┐'
- third += '─┘'
- }
- second += x < table.timewidth - 1 ? ' ├' : ' │'
-
- if( operation.isMultiRegisterOperation ){
-
- let n = ( operation.multiRegisterOperationIndex * 2 ) % ( table[ x ].maximumCharacterWidth + 1 ) + 1
- if( operation.siblingExistsAbove ){
-
- first = first.substring( 0, n ) +'┴'+ first.substring( n + 1 )
- }
- if( operation.siblingExistsBelow ){
-
- third = third.substring( 0, n ) +'┬'+ third.substring( n + 1 )
- }
- }
- }
- const y3 = y * 3
- output[ y3 + 1 ] += first
- output[ y3 + 2 ] += second
- output[ y3 + 3 ] += third
- }
- }
- return '\n'+ output.join( '\n' )
- },
-
-
-
-
- // Oh yes my friends... WebGL is coming!
-
- toShader: function(){
-
- },
- toGoogleCirq: function(){
-/*
-
-
-cirq.GridQubit(4,5)
-
-https://cirq.readthedocs.io/en/stable/tutorial.html
-
-*/
- const header = `import cirq`
-
- return headers
- },
- toAmazonBraket: function(){
- let is_valid_braket_circuit = true
- const header = `import boto3
-from braket.aws import AwsDevice
-from braket.circuits import Circuit
-
-my_bucket = f"amazon-braket-Your-Bucket-Name" # the name of the bucket
-my_prefix = "Your-Folder-Name" # the name of the folder in the bucket
-s3_folder = (my_bucket, my_prefix)\n
-device = LocalSimulator()\n\n`
-//TODO (ltnln): Syntax is different for simulators and actual quantum computers. Should there be a default? Should there be a way to change?
-//vs an actual quantum computer? May not be necessary.
- let variables = ''
- let num_unitaries = 0
- //`qjs_circuit = Circuit().h(0).cnot(0,1)`
- //ltnln change: from gate.AmazonBraketName -> gate.symbolAmazonBraket
- let circuit = this.operations.reduce( function( string, operation ){
- let awsGate = operation.gate.symbolAmazonBraket !== undefined ?
- operation.gate.symbolAmazonBraket :
- operation.gate.symbol.substr( 0, 1 ).toLowerCase()
- if( operation.gate.symbolAmazonBraket === undefined ) is_valid_braket_circuit = false
- if( operation.gate.symbol === 'X' ) {
- if( operation.registerIndices.length === 1 ) awsGate = operation.gate.symbolAmazonBraket
- else if( operation.registerIndices.length === 2 ) awsGate = 'cnot'
- else if( operation.registerIndices.length === 3) awsGate = 'ccnot'
- else is_valid_braket_circuit = false
- }
-
- else if( operation.gate.symbol === 'S' ) {
- if( operation.gate.parameters["phi"] === 0 ) {
- awsGate = operation.registerIndices.length == 2 ? awsGate : "cswap"
- return string +'.'+ awsGate +'(' +
- operation.registerIndices.reduce( function( string, registerIndex, r ){
-
- return string + (( r > 0 ) ? ',' : '' ) + ( registerIndex - 1 )
-
- }, '' ) + ')'
- }
- awsGate = 'pswap'
- }
- //ltnln note: removed the if( operation.gate.symbol == '*') branch as it should be covered by
- //the inclusion of the CURSOR gate.
- else if( operation.gate.symbol === 'Y' || operation.gate.symbol === 'Z' || operation.gate.symbol === 'P' ) {
- if( operation.registerIndices.length === 1) awsGate = operation.gate.symbolAmazonBraket
- else if( operation.registerIndices.length === 2 ) awsGate = (operation.gate.symbol === 'Y') ? 'cy' : (operation.gate.symbol === 'Z') ? 'cz' : 'cphaseshift'
- else is_valid_braket_circuit = false
- }
- //for all unitary gates, there must be a line of code to initialize the matrix for use
- //in Braket's .u(matrix=my_unitary, targets[0]) function
- else if( operation.gate.symbol === 'U') {
- //check that this truly works as a unique id
- is_valid_braket_circuit &= operation.registerIndices.length === 1
- const new_matrix = `unitary_` + num_unitaries
- num_unitaries++
- const a = Q.ComplexNumber.toText(Math.cos(-(operation.gate.parameters[ "phi" ] + operation.gate.parameters[ "lambda" ])*Math.cos(operation.gate.parameters[ "theta" ] / 2) / 2),
- Math.sin(-(operation.gate.parameters[ "phi" ] + operation.gate.parameters[ "lambda" ])*Math.cos(operation.gate.parameters[ "theta" ] / 2) / 2))
- .replace('i', 'j')
- const b = Q.ComplexNumber.toText(-Math.cos(-(operation.gate.parameters[ "phi" ] - operation.gate.parameters[ "lambda" ])*Math.sin(operation.gate.parameters[ "theta" ] / 2) / 2),
- -Math.sin(-(operation.gate.parameters[ "phi" ] - operation.gate.parameters[ "lambda" ])*Math.sin(operation.gate.parameters[ "theta" ] / 2)) / 2)
- .replace('i', 'j')
- const c = Q.ComplexNumber.toText(Math.cos((operation.gate.parameters[ "phi" ] - operation.gate.parameters[ "lambda" ])*Math.sin(operation.gate.parameters[ "theta" ] / 2) / 2),
- -Math.sin((operation.gate.parameters[ "phi" ] - operation.gate.parameters[ "lambda" ])*Math.sin(operation.gate.parameters[ "theta" ] / 2)) / 2)
- .replace('i', 'j')
- const d = Q.ComplexNumber.toText(Math.cos((operation.gate.parameters[ "phi" ] + operation.gate.parameters[ "lambda" ])*Math.cos(operation.gate.parameters[ "theta" ] / 2) / 2),
- Math.sin((operation.gate.parameters[ "phi" ] + operation.gate.parameters[ "lambda" ])*Math.cos(operation.gate.parameters[ "theta" ] / 2)) / 2)
- .replace('i', 'j')
- variables += new_matrix + ` = np.array(` +
- `[[` + a + ', ' + b + `],`+
- `[` + c + ', ' + d + `]])\n`
- return string +'.'+ awsGate +'(' + new_matrix +','+
- operation.registerIndices.reduce( function( string, registerIndex, r ){
-
- return string + (( r > 0 ) ? ',' : '' ) + ( registerIndex - 1 )
-
- }, '' ) + ')'
- }
- // I believe this line should ensure that we don't include any controlled single-qubit gates that aren't allowed in Braket.
- // The registerIndices.length > 1 technically shouldn't be necessary, but if changes are made later, it's just for safety.
- else is_valid_braket_circuit &= (operation.registerIndices.length === 1) || ( operation.registerIndices.length > 1 && operation.gate.is_multi_qubit )
- return string +'.'+ awsGate +'(' +
- operation.registerIndices.reduce( function( string, registerIndex, r ){
-
- return string + (( r > 0 ) ? ',' : '' ) + ( registerIndex - 1 )
-
- }, '' ) + ((operation.gate.has_parameters) ?
- Object.values( operation.gate.parameters ).reduce( function( string, parameter ) {
- return string + "," + parameter
- }, '')
- : '') + ')'
-
- }, 'qjs_circuit = Circuit()' )
- variables += '\n'
- if( this.operations.length === 0 ) circuit += '.i(0)'// Quick fix to avoid an error here!
-
- const footer = `\n\ntask = device.run(qjs_circuit, s3_folder, shots=100)
-print(task.result().measurement_counts)`
- return is_valid_braket_circuit ? header + variables + circuit + footer : `###This circuit is not representable as a Braket circuit!###`
- },
- toLatex: function(){
-
- /*
-
- \Qcircuit @C=1em @R=.7em {
- & \ctrl{2} & \targ & \gate{U} & \qw \\
- & \qw & \ctrl{-1} & \qw & \qw \\
- & \targ & \ctrl{-1} & \ctrl{-2} & \qw \\
- & \qw & \ctrl{-1} & \qw & \qw
- }
-
- No "&"" means it’s an input. So could also do this:
- \Qcircuit @C=1.4em @R=1.2em {
-
- a & i \\
- 1 & x
- }
- */
-
- return '\\Qcircuit @C=1.0em @R=0.7em {\n' +
- this.toTable()
- .reduce( function( array, moment, m ){
-
- moment.forEach( function( operation, o, operations ){
-
- let command = 'qw'
- if( operation.symbol !== 'I' ){
-
- if( operation.isMultiRegisterOperation ){
-
- if( operation.indexAmongSiblings === 0 ){
-
- if( operation.symbol === 'X' ) command = 'targ'
- else command = operation.symbol.toLowerCase()
- }
- else if( operation.indexAmongSiblings > 0 ) command = 'ctrl{?}'
- }
- else command = operation.symbol.toLowerCase()
- }
- operations[ o ].latexCommand = command
- })
- const maximumCharacterWidth = moment.reduce( function( maximumCharacterWidth, operation ){
-
- return Math.max( maximumCharacterWidth, operation.latexCommand.length )
-
- }, 0 )
- moment.forEach( function( operation, o ){
-
- array[ o ] += '& \\'+ operation.latexCommand.padEnd( maximumCharacterWidth ) +' '
- })
- return array
-
- }, new Array( this.bandwidth ).fill( '\n\t' ))
- .join( '\\\\' ) +
- '\n}'
- },
-
-
-
-
-
-
- //////////////
- // //
- // Edit //
- // //
- //////////////
-
-
- get: function( momentIndex, registerIndex ){
-
- return this.operations.find( function( op ){
-
- return op.momentIndex === momentIndex &&
- op.registerIndices.includes( registerIndex )
- })
- },
- clear$: function( momentIndex, registerIndices ){
-
- const circuit = this
-
-
- // Validate our arguments.
-
- if( arguments.length !== 2 )
- Q.warn( `Q.Circuit.clear$ expected 2 arguments but received ${ arguments.length }.` )
- if( Q.isUsefulInteger( momentIndex ) !== true )
- return Q.error( `Q.Circuit attempted to clear an input on Circuit #${ circuit.index } using an invalid moment index:`, momentIndex )
- if( Q.isUsefulInteger( registerIndices )) registerIndices = [ registerIndices ]
- if( registerIndices instanceof Array !== true )
- return Q.error( `Q.Circuit attempted to clear an input on Circuit #${ circuit.index } using an invalid register indices array:`, registerIndices )
-
-
- // Let’s find any operations
- // with a footprint at this moment index and one of these register indices
- // and collect not only their content, but their index in the operations array.
- // (We’ll need that index to splice the operations array later.)
-
- const foundOperations = circuit.operations.reduce( function( filtered, operation, o ){
-
- if( operation.momentIndex === momentIndex &&
- operation.registerIndices.some( function( registerIndex ){
-
- return registerIndices.includes( registerIndex )
- })
- ) filtered.push({
-
- index: o,
- momentIndex: operation.momentIndex,
- registerIndices: operation.registerIndices,
- gate: operation.gate
- })
- return filtered
-
- }, [] )
-
-
- // Because we held on to each found operation’s index
- // within the circuit’s operations array
- // we can now easily splice them out of the array.
-
- foundOperations.reduce( function( deletionsSoFar, operation ){
-
- circuit.operations.splice( operation.index - deletionsSoFar, 1 )
- return deletionsSoFar + 1
-
- }, 0 )
-
-
- // IMPORTANT!
- // Operations must be sorted properly
- // for toTable to work reliably with
- // multi-register operations!!
-
- this.sort$()
-
-
- // Let’s make history.
-
- if( foundOperations.length ){
-
- this.history.record$({
-
- redo: {
-
- name: 'clear$',
- func: circuit.clear$,
- args: Array.from( arguments )
- },
- undo: foundOperations.reduce( function( undos, operation ){
-
- undos.push({
-
- name: 'set$',
- func: circuit.set$,
- args: [
-
- operation.gate,
- operation.momentIndex,
- operation.registerIndices
- ]
- })
- return undos
-
- }, [] )
- })
-
-
- // Let anyone listening,
- // including any circuit editor interfaces,
- // know about what we’ve just completed here.
-
- foundOperations.forEach( function( operation ){
-
- window.dispatchEvent( new CustomEvent(
-
- 'Q.Circuit.clear$', { detail: {
-
- circuit,
- momentIndex,
- registerIndices: operation.registerIndices
- }}
- ))
- })
- }
-
-
- // Enable that “fluent interface” method chaining :)
-
- return circuit
- },
-
-
- setProperty$: function( key, value ){
-
- this[ key ] = value
- return this
- },
- setName$: function( name ){
-
- if( typeof name === 'function' ) name = name()
- return this.setProperty$( 'name', name )
- },
-
-
- set$: function( gate, momentIndex, registerIndices, parameters = {} ){
-
- const circuit = this
- // Is this a valid gate?
- // We clone the gate rather than using the constant; this way, if we change it's parameters, we don't change the constant.
- if( typeof gate === 'string' ) gate = Q.Gate.prototype.clone( Q.Gate.findBySymbol( gate ) )
- if( gate instanceof Q.Gate !== true ) return Q.error( `Q.Circuit attempted to add a gate (${ gate }) to circuit #${ this.index } at moment #${ momentIndex } that is not a gate:`, gate )
-
-
- // Is this a valid moment index?
-
- if( Q.isUsefulNumber( momentIndex ) !== true ||
- Number.isInteger( momentIndex ) !== true ||
- momentIndex < 1 || momentIndex > this.timewidth ){
-
- return Q.error( `Q.Circuit attempted to add a gate to circuit #${ this.index } at a moment index that is not valid:`, momentIndex )
- }
-
-
- // Are these valid register indices?
-
- if( typeof registerIndices === 'number' ) registerIndices = [ registerIndices ]
- if( registerIndices instanceof Array !== true ) return Q.error( `Q.Circuit attempted to add a gate to circuit #${ this.index } at moment #${ momentIndex } with an invalid register indices array:`, registerIndices )
- if( registerIndices.length === 0 ) return Q.error( `Q.Circuit attempted to add a gate to circuit #${ this.index } at moment #${ momentIndex } with an empty register indices array:`, registerIndices )
- if( registerIndices.reduce( function( accumulator, registerIndex ){
-
- // console.log(accumulator &&
- // registerIndex > 0 &&
- // registerIndex <= circuit.bandwidth)
- return (
-
- accumulator &&
- registerIndex > 0 &&
- registerIndex <= circuit.bandwidth
- )
-
- }, false )){
-
- return Q.warn( `Q.Circuit attempted to add a gate to circuit #${ this.index } at moment #${ momentIndex } with some out of range qubit indices:`, registerIndices )
- }
-
-
- // Ok, now we can check if this set$ command
- // is redundant.
-
- const
- isRedundant = !!circuit.operations.find( function( operation ){
-
- return (
-
- momentIndex === operation.momentIndex &&
- gate === operation.gate &&
- registerIndices.length === operation.registerIndices.length &&
- registerIndices.every( val => operation.registerIndices.includes( val ))
- )
- })
-
-
- // If it’s NOT redundant
- // then we’re clear to proceed.
-
- if( isRedundant !== true ){
-
-
- // If there’s already an operation here,
- // we’d better get rid of it!
- // This will also entirely remove any multi-register operations
- // that happen to have a component at this moment / register.
-
- this.clear$( momentIndex, registerIndices )
-
-
- // Finally.
- // Finally we can actually set this operation.
- // Aren’t you glad we handle all this for you?
-
- const
- //TODO: For ltnln (have to fix)
- // a) allow users to control whatever they want! Just because it's not allowed in Braket
- // doesn't mean they shouldn't be allowed to do it in Q! (Probably fixable by adjusting toAmazonBraket)
- // b) Controlling a multi_qubit gate will not treat the control icon like a control gate!
- isControlled = registerIndices.length > 1 && gate !== Q.Gate.SWAP && gate.can_be_controlled !== undefined
- operation = {
-
- gate,
- momentIndex,
- registerIndices,
- isControlled
- }
- //perform parameter update here!!!
- Object.keys(parameters).forEach( element => { parameters[element] = +parameters[element] })
- if(gate.has_parameters) gate.updateMatrix$.apply( gate, Object.values(parameters) )
- this.operations.push( operation )
-
-
- // IMPORTANT!
- // Operations must be sorted properly
- // for toTable to work reliably with
- // multi-register operations!!
-
- this.sort$()
-
-
- // Let’s make history.
- const redo_args = Array.from( arguments )
- Object.assign( redo_args[ redo_args.length - 1 ], parameters )
- this.history.record$({
-
- redo: {
-
- name: 'set$',
- func: circuit.set$,
- args: redo_args
- },
- undo: [{
-
- name: 'clear$',
- func: circuit.clear$,
- args: [ momentIndex, registerIndices ]
- }]
- })
-
-
- // Emit an event that we have set an operation
- // on this circuit.
-
- window.dispatchEvent( new CustomEvent(
-
- 'Q.Circuit.set$', { detail: {
-
- circuit,
- operation
- }}
- ))
- }
- return circuit
- },
-
-
-
-
- determineRanges: function( options ){
-
- if( options === undefined ) options = {}
- let {
-
- qubitFirstIndex,
- qubitRange,
- qubitLastIndex,
- momentFirstIndex,
- momentRange,
- momentLastIndex
-
- } = options
-
- if( typeof qubitFirstIndex !== 'number' ) qubitFirstIndex = 0
- if( typeof qubitLastIndex !== 'number' && typeof qubitRange !== 'number' ) qubitLastIndex = this.bandwidth
- if( typeof qubitLastIndex !== 'number' && typeof qubitRange === 'number' ) qubitLastIndex = qubitFirstIndex + qubitRange
- else if( typeof qubitLastIndex === 'number' && typeof qubitRange !== 'number' ) qubitRange = qubitLastIndex - qubitFirstIndex
- else return Q.error( `Q.Circuit attempted to copy a circuit but could not understand what qubits to copy.` )
-
- if( typeof momentFirstIndex !== 'number' ) momentFirstIndex = 0
- if( typeof momentLastIndex !== 'number' && typeof momentRange !== 'number' ) momentLastIndex = this.timewidth
- if( typeof momentLastIndex !== 'number' && typeof momentRange === 'number' ) momentLastIndex = momentFirstIndex + momentRange
- else if( typeof momentLastIndex === 'number' && typeof momentRange !== 'number' ) momentRange = momentLastIndex - momentFirstIndex
- else return Q.error( `Q.Circuit attempted to copy a circuit but could not understand what moments to copy.` )
-
- Q.log( 0.8,
-
- '\nQ.Circuit copy operation:',
- '\n\n qubitFirstIndex', qubitFirstIndex,
- '\n qubitLastIndex ', qubitLastIndex,
- '\n qubitRange ', qubitRange,
- '\n\n momentFirstIndex', momentFirstIndex,
- '\n momentLastIndex ', momentLastIndex,
- '\n momentRange ', momentRange,
- '\n\n'
- )
-
- return {
+ if( direction < 0 ) this.index --
+
- qubitFirstIndex,
- qubitRange,
- qubitLastIndex,
- momentFirstIndex,
- momentRange,
- momentLastIndex
- }
+ // It’s now safe to turn recording back on.
+
+ this.isRecording = true
+
+
+ // Emit an event so the GUI or anyone else listening
+ // can know if we have available undo or redo commands
+ // based on where or index is.
+
+ this.assess()
+ return true
},
+ undo$: function(){ return this.step$( -1 )},
+ redo$: function(){ return this.step$( 1 )},
+ report: function(){
+ const argsParse = function( output, entry, i ){
- copy: function( options, isACutOperation ){
+ if( i > 0 ) output += ', '
+ return output + ( typeof entry === 'object' && entry.name ? entry.name : entry )
+ }
+ return this.entries.reduce( function( output, entry, i ){
- const original = this
- let {
+ output += '\n\n'+ i + ' ════════════════════════════════════════'+
+ entry.reduce( function( output, entry, i ){
- registerFirstIndex,
- registerRange,
- registerLastIndex,
- momentFirstIndex,
- momentRange,
- momentLastIndex
+ output += '\n\n '+ i +' ────────────────────────────────────────\n'
+ if( entry.redo ){
+
+ output += '\n ⟳ Redo ── '+ entry.redo.name +' '
+ if( entry.redo.args ) output += entry.redo.args.reduce( argsParse, '' )
+ }
+ output += entry.undo.reduce( function( output, entry, i ){
- } = this.determineRanges( options )
+ output += '\n ⟲ Undo '+ i +' ── '+ entry.name +' '
+ if( entry.args ) output += entry.args.reduce( argsParse, '' )
+ return output
- const copy = new Q.Circuit( registerRange, momentRange )
+ }, '' )
- original.operations
- .filter( function( operation ){
+ return output
- return ( operation.registerIndices.every( function( registerIndex ){
+ }, '' )
+ return output
+
+ }, 'History entry cursor: '+ this.index )
+ }
+})
- return (
- operation.momentIndex >= momentFirstIndex &&
- operation.momentIndex < momentLastIndex &&
- operation.registerIndex >= registerFirstIndex &&
- operation.registerIndex < registerLastIndex
- )
- }))
- })
- .forEach( function( operation ){
- const adjustedRegisterIndices = operation.registerIndices.map( function( registerIndex ){
+module.exports = { History };
+},{"./Misc":5}],10:[function(require,module,exports){
+// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
- return registerIndex - registerFirstIndex
- })
- copy.set$(
+const logger = require('./Logging');
+const {ComplexNumber} = require('./Q-ComplexNumber');
+
+Matrix = function () {
+ // We’re keeping track of how many matrices are
+ // actually being generated. Just curiosity.
+
+ this.index = Matrix.index++;
+
+ let matrixWidth = null;
+
+ // Has Matrix been called with two numerical arguments?
+ // If so, we need to create an empty Matrix
+ // with dimensions of those values.
+
+ if (arguments.length == 1 && ComplexNumber.isNumberLike(arguments[0])) {
+ matrixWidth = arguments[0];
+ this.rows = new Array(matrixWidth).fill(0).map(function () {
+ return new Array(matrixWidth).fill(0);
+ });
+ } else if (
+ arguments.length == 2 &&
+ ComplexNumber.isNumberLike(arguments[0]) &&
+ ComplexNumber.isNumberLike(arguments[1])
+ ) {
+ matrixWidth = arguments[0];
+ this.rows = new Array(arguments[1]).fill(0).map(function () {
+ return new Array(matrixWidth).fill(0);
+ });
+ } else {
+ // Matrices’ primary organization is by rows,
+ // which is more congruent with our written langauge;
+ // primarily organizated by horizontally juxtaposed glyphs.
+ // That means it’s easier to write an instance invocation in code
+ // and easier to read when inspecting properties in the console.
+
+ let matrixWidthIsBroken = false;
+ this.rows = Array.from(arguments);
+ this.rows.forEach(function (row) {
+ if (row instanceof Array !== true) row = [row];
+ if (matrixWidth === null) matrixWidth = row.length;
+ else if (matrixWidth !== row.length) matrixWidthIsBroken = true;
+ });
+ if (matrixWidthIsBroken)
+ return logger.error(
+ `Matrix found upon initialization that matrix#${this.index} row lengths were not equal. You are going to have a bad time.`,
+ this
+ );
+ }
+
+ // But for convenience we can also organize by columns.
+ // Note this represents the transposed version of itself!
+
+ const matrix = this;
+ this.columns = [];
+ for (let x = 0; x < matrixWidth; x++) {
+ const column = [];
+ for (let y = 0; y < this.rows.length; y++) {
+ // Since we’re combing through here
+ // this is a good time to convert Number to ComplexNumber!
+
+ const value = matrix.rows[y][x];
+ if (typeof value === "number") {
+ // console.log('Created a complex number!')
+ matrix.rows[y][x] = new ComplexNumber(value);
+ } else if (value instanceof ComplexNumber === false) {
+ return logger.error(
+ `Matrix found upon initialization that matrix#${this.index} contained non-quantitative values. A+ for creativity, but F for functionality.`,
+ this
+ );
+ }
+
+ // console.log( x, y, matrix.rows[ y ][ x ])
+
+ Object.defineProperty(column, y, {
+ get: function () {
+ return matrix.rows[y][x];
+ },
+ set: function (n) {
+ matrix.rows[y][x] = n;
+ },
+ });
+ }
+ this.columns.push(column);
+ }
+};
- operation.gate,
- 1 + m - momentFirstIndex,
- adjustedRegisterIndices
- )
- })
+///////////////////////////
+// //
+// Static properties //
+// //
+///////////////////////////
+Object.assign(Matrix, {
+ index: 0,
+ help: function () {
+ return logger.help(this);
+ },
+ constants: {}, // Only holds references; an easy way to look up what constants exist.
+ createConstant: function (key, value) {
+ this[key] = value;
+ this.constants[key] = this[key];
+ Object.freeze(this[key]);
+ },
+ createConstants: function () {
+ if (arguments.length % 2 !== 0) {
+ return logger.error(
+ "Q attempted to create constants with invalid (KEY, VALUE) pairs."
+ );
+ }
+ for (let i = 0; i < arguments.length; i += 2) {
+ this.createConstant(arguments[i], arguments[i + 1]);
+ }
+ },
+
+ isMatrixLike: function (obj) {
+ //return obj instanceof Matrix || Matrix.prototype.isPrototypeOf( obj )
+ return obj instanceof this || this.prototype.isPrototypeOf(obj);
+ },
+ isWithinRange: function (n, minimum, maximum) {
+ return (
+ typeof n === "number" && n >= minimum && n <= maximum && n == parseInt(n)
+ );
+ },
+ getWidth: function (matrix) {
+ return matrix.columns.length;
+ },
+ getHeight: function (matrix) {
+ return matrix.rows.length;
+ },
+ haveEqualDimensions: function (matrix0, matrix1) {
+ return (
+ matrix0.rows.length === matrix1.rows.length &&
+ matrix0.columns.length === matrix1.columns.length
+ );
+ },
+ areEqual: function (matrix0, matrix1) {
+ if (matrix0 instanceof Matrix !== true) return false;
+ if (matrix1 instanceof Matrix !== true) return false;
+ if (Matrix.haveEqualDimensions(matrix0, matrix1) !== true) return false;
+ return matrix0.rows.reduce(function (state, row, r) {
+ return (
+ state &&
+ row.reduce(function (state, cellValue, c) {
+ return state && cellValue.isEqualTo(matrix1.rows[r][c]);
+ }, true)
+ );
+ }, true);
+ },
+
+ createSquare: function (size, f) {
+ if (typeof size !== "number") size = 2;
+ if (typeof f !== "function")
+ f = function () {
+ return 0;
+ };
+ const data = [];
+ for (let y = 0; y < size; y++) {
+ const row = [];
+ for (let x = 0; x < size; x++) {
+ row.push(f(x, y));
+ }
+ data.push(row);
+ }
+ return new Matrix(...data);
+ },
+ createZero: function (size) {
+ return new Matrix.createSquare(size);
+ },
+ createOne: function (size) {
+ return new Matrix.createSquare(size, function () {
+ return 1;
+ });
+ },
+ createIdentity: function (size) {
+ return new Matrix.createSquare(size, function (x, y) {
+ return x === y ? 1 : 0;
+ });
+ },
+
+ // Import FROM a format.
+
+ from: function (format) {
+ if (typeof format !== "string") format = "Array";
+ const f = Matrix["from" + format];
+ format = format.toLowerCase();
+ if (typeof f !== "function")
+ return logger.error(
+ `Matrix could not find an importer for “${format}” data.`
+ );
+ return f;
+ },
+ fromArray: function (array) {
+ return new Matrix(...array);
+ },
+ fromXsv: function (input, rowSeparator, valueSeparator) {
+ `
+ Ingest string data organized by row, then by column
+ where rows are separated by one token (default: \n)
+ and column values are separated by another token
+ (default: \t).
- // The cut$() operation just calls copy()
- // with the following boolean set to true.
- // If this is a cut we need to
- // replace all gates in this area with identity gates.
+ `;
+
+ if (typeof rowSeparator !== "string") rowSeparator = "\n";
+ if (typeof valueSeparator !== "string") valueSeparator = "\t";
+
+ const inputRows = input.split(rowSeparator),
+ outputRows = [];
+
+ inputRows.forEach(function (inputRow) {
+ inputRow = inputRow.trim();
+ if (inputRow === "") return;
+
+ const outputRow = [];
+ inputRow.split(valueSeparator).forEach(function (cellValue) {
+ outputRow.push(parseFloat(cellValue));
+ });
+ outputRows.push(outputRow);
+ });
+ return new Matrix(...outputRows);
+ },
+ fromCsv: function (csv) {
+ return Matrix.fromXsv(csv.replace(/\r/g, "\n"), "\n", ",");
+ },
+ fromTsv: function (tsv) {
+ return Matrix.fromXsv(tsv, "\n", "\t");
+ },
+ fromHtml: function (html) {
+ return Matrix.fromXsv(
+ html
+ .replace(/\r?\n|\r||/g, "")
+ .replace(/<\/td>(\s*)<\/tr>/g, " ")
+ .match(/(.*)<\/table>/i)[1],
+ "",
+ ""
+ );
+ },
+
+ // Export TO a format.
+
+ toXsv: function (matrix, rowSeparator, valueSeparator) {
+ return matrix.rows.reduce(function (xsv, row) {
+ return (
+ xsv +
+ rowSeparator +
+ row.reduce(function (xsv, cell, c) {
+ return xsv + (c > 0 ? valueSeparator : "") + cell.toText();
+ }, "")
+ );
+ }, "");
+ },
+ toCsv: function (matrix) {
+ return Matrix.toXsv(matrix, "\n", ",");
+ },
+ toTsv: function (matrix) {
+ return Matrix.toXsv(matrix, "\n", "\t");
+ },
+
+ // Operate NON-destructive.
+
+ add: function (matrix0, matrix1) {
+ if (
+ Matrix.isMatrixLike(matrix0) !== true ||
+ Matrix.isMatrixLike(matrix1) !== true
+ ) {
+ return logger.error(
+ `Matrix attempted to add something that was not a matrix.`
+ );
+ }
+ if (Matrix.haveEqualDimensions(matrix0, matrix1) !== true)
+ return logger.error(
+ `Matrix cannot add matrix#${matrix0.index} of dimensions ${matrix0.columns.length}x${matrix0.rows.length} to matrix#${matrix1.index} of dimensions ${matrix1.columns.length}x${matrix1.rows.length}.`
+ );
+
+ return new Matrix(
+ ...matrix0.rows.reduce(function (resultMatrixRow, row, r) {
+ resultMatrixRow.push(
+ row.reduce(function (resultMatrixColumn, cellValue, c) {
+ // resultMatrixColumn.push( cellValue + matrix1.rows[ r ][ c ])
+ resultMatrixColumn.push(cellValue.add(matrix1.rows[r][c]));
+ return resultMatrixColumn;
+ }, [])
+ );
+ return resultMatrixRow;
+ }, [])
+ );
+ },
+ multiplyScalar: function (matrix, scalar) {
+ if (Matrix.isMatrixLike(matrix) !== true) {
+ return logger.error(
+ `Matrix attempted to scale something that was not a matrix.`
+ );
+ }
+ if (typeof scalar !== "number") {
+ return logger.error(
+ `Matrix attempted to scale this matrix#${matrix.index} by an invalid scalar: ${scalar}.`
+ );
+ }
+ return new Matrix(
+ ...matrix.rows.reduce(function (resultMatrixRow, row) {
+ resultMatrixRow.push(
+ row.reduce(function (resultMatrixColumn, cellValue) {
+ // resultMatrixColumn.push( cellValue * scalar )
+ resultMatrixColumn.push(cellValue.multiply(scalar));
+ return resultMatrixColumn;
+ }, [])
+ );
+ return resultMatrixRow;
+ }, [])
+ );
+ },
+ multiply: function (matrix0, matrix1) {
+ `
+ Two matrices can be multiplied only when
+ the number of columns in the first matrix
+ equals the number of rows in the second matrix.
+ Reminder: Matrix multiplication is not commutative
+ so the order in which you multiply matters.
- // UPDATE !!!!
- // will come back to fix!!
- // with new style it's now just a matter of
- // splicing out these out of circuit.operations
+ SEE ALSO
-
- if( isACutOperation === true ){
+ https://en.wikipedia.org/wiki/Matrix_multiplication
+ `;
+
+ if (
+ Matrix.isMatrixLike(matrix0) !== true ||
+ Matrix.isMatrixLike(matrix1) !== true
+ ) {
+ return logger.error(
+ `Matrix attempted to multiply something that was not a matrix.`
+ );
+ }
+ if (matrix0.columns.length !== matrix1.rows.length) {
+ return logger.error(
+ `Matrix attempted to multiply Matrix#${matrix0.index}(cols==${matrix0.columns.length}) by Matrix#${matrix1.index}(rows==${matrix1.rows.length}) but their dimensions were not compatible for this.`
+ );
+ }
+ const resultMatrix = [];
+ matrix0.rows.forEach(function (matrix0Row) {
+ // Each row of THIS matrix
+
+ const resultMatrixRow = [];
+ matrix1.columns.forEach(function (matrix1Column) {
+ // Each column of OTHER matrix
+
+ const sum = new ComplexNumber();
+ matrix1Column.forEach(function (matrix1CellValue, index) {
+ // Work down the column of OTHER matrix
+
+ sum.add$(matrix0Row[index].multiply(matrix1CellValue));
+ });
+ resultMatrixRow.push(sum);
+ });
+ resultMatrix.push(resultMatrixRow);
+ });
+ //return new Matrix( ...resultMatrix )
+ return new this(...resultMatrix);
+ },
+ multiplyTensor: function (matrix0, matrix1) {
+ `
+ https://en.wikipedia.org/wiki/Kronecker_product
+ https://en.wikipedia.org/wiki/Tensor_product
+ `;
+
+ if (
+ Matrix.isMatrixLike(matrix0) !== true ||
+ Matrix.isMatrixLike(matrix1) !== true
+ ) {
+ return logger.error(
+ `Matrix attempted to tensor something that was not a matrix.`
+ );
+ }
+
+ const resultMatrix = [],
+ resultMatrixWidth = matrix0.columns.length * matrix1.columns.length,
+ resultMatrixHeight = matrix0.rows.length * matrix1.rows.length;
+
+ for (let y = 0; y < resultMatrixHeight; y++) {
+ const resultMatrixRow = [];
+ for (let x = 0; x < resultMatrixWidth; x++) {
+ const matrix0X = Math.floor(x / matrix0.columns.length),
+ matrix0Y = Math.floor(y / matrix0.rows.length),
+ matrix1X = x % matrix1.columns.length,
+ matrix1Y = y % matrix1.rows.length;
+
+ resultMatrixRow.push(
+ //matrix0.rows[ matrix0Y ][ matrix0X ] * matrix1.rows[ matrix1Y ][ matrix1X ]
+ matrix0.rows[matrix0Y][matrix0X].multiply(
+ matrix1.rows[matrix1Y][matrix1X]
+ )
+ );
+ }
+ resultMatrix.push(resultMatrixRow);
+ }
+ return new Matrix(...resultMatrix);
+ },
+});
- /*
- for( let m = momentFirstIndex; m < momentLastIndex; m ++ ){
+//////////////////////////////
+// //
+// Prototype properties //
+// //
+//////////////////////////////
- original.moments[ m ] = new Array( original.bandwidth )
- .fill( 0 )
- .map( function( qubit, q ){
+Object.assign(Matrix.prototype, {
+ isValidRow: function (r) {
+ return Matrix.isWithinRange(r, 0, this.rows.length - 1);
+ },
+ isValidColumn: function (c) {
+ return Matrix.isWithinRange(c, 0, this.columns.length - 1);
+ },
+ isValidAddress: function (x, y) {
+ return this.isValidRow(y) && this.isValidColumn(x);
+ },
+ getWidth: function () {
+ return Matrix.getWidth(this);
+ },
+ getHeight: function () {
+ return Matrix.getHeight(this);
+ },
+
+ // Read NON-destructive by nature. (Except quantum reads of course! ROFL!!)
+
+ read: function (x, y) {
+ `
+ Equivalent to
+ this.columns[ x ][ y ]
+ or
+ this.rows[ y ][ x ]
+ but with safety checks.
+ `;
+
+ if (this.isValidAddress(x, y)) return this.rows[y][x];
+ return logger.error(
+ `Matrix could not read from cell address (x=${x}, y=${y}) in matrix#${this.index}.`,
+ this
+ );
+ },
+ clone: function () {
+ return new Matrix(...this.rows);
+ },
+ isEqualTo: function (otherMatrix) {
+ return Matrix.areEqual(this, otherMatrix);
+ },
+
+ toArray: function () {
+ return this.rows;
+ },
+ toXsv: function (rowSeparator, valueSeparator) {
+ return Matrix.toXsv(this, rowSeparator, valueSeparator);
+ },
+ toCsv: function () {
+ return Matrix.toXsv(this, "\n", ",");
+ },
+ toTsv: function () {
+ return Matrix.toXsv(this, "\n", "\t");
+ },
+ toHtml: function () {
+ return (
+ this.rows.reduce(function (html, row) {
+ return (
+ html +
+ row.reduce(function (html, cell) {
+ return html + "\n\t\t" + cell.toText() + " ";
+ }, "\n\t") +
+ "\n\t "
+ );
+ }, "\n"
+ );
+ },
+
+ // Write is DESTRUCTIVE by nature. Not cuz I hate ya.
+
+ write$: function (x, y, n) {
+ `
+ Equivalent to
+ this.columns[ x ][ y ] = n
+ or
+ this.rows[ y ][ x ] = n
+ but with safety checks.
+ `;
+
+ if (this.isValidAddress(x, y)) {
+ if (ComplexNumber.isNumberLike(n)) n = new ComplexNumber(n);
+ if (n instanceof ComplexNumber !== true)
+ return logger.error(
+ `Attempted to write an invalid value (${n}) to matrix#${this.index} at x=${x}, y=${y}`,
+ this
+ );
+ this.rows[y][x] = n;
+ return this;
+ }
+ return logger.error(
+ `Invalid cell address for Matrix#${this.index}: x=${x}, y=${y}`,
+ this
+ );
+ },
+ copy$: function (matrix) {
+ if (Matrix.isMatrixLike(matrix) !== true)
+ return logger.error(
+ `Matrix attempted to copy something that was not a matrix in to this matrix#${matrix.index}.`,
+ this
+ );
+
+ if (Matrix.haveEqualDimensions(matrix, this) !== true)
+ return logger.error(
+ `Matrix cannot copy matrix#${matrix.index} of dimensions ${matrix.columns.length}x${matrix.rows.length} in to this matrix#${this.index} of dimensions ${this.columns.length}x${this.rows.length} because their dimensions do not match.`,
+ this
+ );
+
+ const that = this;
+ matrix.rows.forEach(function (row, r) {
+ row.forEach(function (n, c) {
+ that.rows[r][c] = n;
+ });
+ });
+ return this;
+ },
+ fromArray$: function (array) {
+ return this.copy$(Matrix.fromArray(array));
+ },
+ fromCsv$: function (csv) {
+ return this.copy$(Matrix.fromCsv(csv));
+ },
+ fromTsv$: function (tsv) {
+ return this.copy$(Matrix.fromTsv(tsv));
+ },
+ fromHtml$: function (html) {
+ return this.copy$(Matrix.fromHtml(html));
+ },
+
+ // Operate NON-destructive.
+
+ add: function (otherMatrix) {
+ return Matrix.add(this, otherMatrix);
+ },
+ multiplyScalar: function (scalar) {
+ return Matrix.multiplyScalar(this, scalar);
+ },
+ multiply: function (otherMatrix) {
+ return Matrix.multiply(this, otherMatrix);
+ },
+ multiplyTensor: function (otherMatrix) {
+ return Matrix.multiplyTensor(this, otherMatrix);
+ },
+
+ // Operate DESTRUCTIVE.
+
+ add$: function (otherMatrix) {
+ return this.copy$(this.add(otherMatrix));
+ },
+ multiplyScalar$: function (scalar) {
+ return this.copy$(this.multiplyScalar(scalar));
+ },
+});
- return {
+//////////////////////////
+// //
+// Static constants //
+// //
+//////////////////////////
- gate: Q.Gate.IDENTITY,
- registerIndices: [ q ]
- }
- })
- }*/
- }
- return copy
- },
- cut$: function( options ){
+Matrix.createConstants(
+ "IDENTITY_2X2",
+ Matrix.createIdentity(2),
+ "IDENTITY_3X3",
+ Matrix.createIdentity(3),
+ "IDENTITY_4X4",
+ Matrix.createIdentity(4),
+
+ "CONSTANT0_2X2",
+ new Matrix([1, 1], [0, 0]),
+
+ "CONSTANT1_2X2",
+ new Matrix([0, 0], [1, 1]),
+
+ "NEGATION_2X2",
+ new Matrix([0, 1], [1, 0]),
+
+ "TEST_MAP_9X9",
+ new Matrix(
+ [11, 21, 31, 41, 51, 61, 71, 81, 91],
+ [12, 22, 32, 42, 52, 62, 72, 82, 92],
+ [13, 23, 33, 43, 53, 63, 73, 83, 93],
+ [14, 24, 34, 44, 54, 64, 74, 84, 94],
+ [15, 25, 35, 45, 55, 65, 75, 85, 95],
+ [16, 26, 36, 46, 56, 66, 76, 86, 96],
+ [17, 27, 37, 47, 57, 67, 77, 87, 97],
+ [18, 28, 38, 48, 58, 68, 78, 88, 98],
+ [19, 29, 39, 49, 59, 69, 79, 89, 99]
+ )
+);
+
+module.exports = { Matrix };
+},{"./Logging":3,"./Q-ComplexNumber":7}],11:[function(require,module,exports){
+// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+const { Matrix } = require("./Q-Matrix");
+const { Gate } = require("./Q-Gate");
+const { ComplexNumber } = require("./Q-ComplexNumber");
+const misc = require("./Misc");
+const logger = require("./Logging");
+
+Qubit = function (a, b, symbol, name) {
+ // If we’ve received an instance of Matrix as our first argument
+ // then we’ll assume there are no further arguments
+ // and just use that matrix as our new Qubit instance.
+
+ if (Matrix.isMatrixLike(a) && b === undefined) {
+ b = a.rows[1][0];
+ a = a.rows[0][0];
+ } else {
+ // All of our internal math now uses complex numbers
+ // rather than Number literals
+ // so we’d better convert!
+
+ if (typeof a === "number") a = new ComplexNumber(a, 0);
+ if (typeof b === "number") b = new ComplexNumber(b, 0);
+
+ // If we receive undefined (or garbage inputs)
+ // let’s try to make it useable.
+ // This way we can always call Qubit with no arguments
+ // to make a new qubit available for computing with.
+
+ if (a instanceof ComplexNumber !== true) a = new ComplexNumber(1, 0);
+ if (b instanceof ComplexNumber !== true) {
+ // 1 - |𝒂|² = |𝒃|²
+ // So this does NOT account for if 𝒃 ought to be imaginary or not.
+ // Perhaps for completeness we could randomly decide
+ // to flip the real and imaginary components of 𝒃 after this line?
+
+ b = ComplexNumber.ONE.subtract(Math.pow(a.absolute(), 2)).squareRoot();
+ }
+ }
+
+ // Sanity check!
+ // Does this constraint hold true? |𝒂|² + |𝒃|² = 1
+
+ if (
+ Math.pow(a.absolute(), 2) + Math.pow(b.absolute(), 2) - 1 >
+ misc.constants.EPSILON
+ )
+ return logger.error(
+ `Qubit could not accept the initialization values of a=${a} and b=${b} because their squares do not add up to 1.`
+ );
+
+ Matrix.call(this, [a], [b]);
+ this.index = Qubit.index++;
+
+ // Convenience getters and setters for this qubit’s
+ // controll bit and target bit.
+
+ Object.defineProperty(this, "alpha", {
+ get: function () {
+ return this.rows[0][0];
+ },
+ set: function (n) {
+ this.rows[0][0] = n;
+ },
+ });
+ Object.defineProperty(this, "beta", {
+ get: function () {
+ return this.rows[1][0];
+ },
+ set: function (n) {
+ this.rows[1][0] = n;
+ },
+ });
+
+ // Used for Dirac notation: |?⟩
+
+ if (typeof symbol === "string") this.symbol = symbol;
+ if (typeof name === "string") this.name = name;
+ if (this.symbol === undefined || this.name === undefined) {
+ const found = Object.values(Qubit.constants).find(function (qubit) {
+ return a.isEqualTo(qubit.alpha) && b.isEqualTo(qubit.beta);
+ });
+ if (found === undefined) {
+ this.symbol = "?";
+ this.name = "Unnamed";
+ } else {
+ if (this.symbol === undefined) this.symbol = found.symbol;
+ if (this.name === undefined) this.name = found.name;
+ }
+ }
+};
+//Qubit inherits from Matrix.
+Qubit.prototype = Object.create(Matrix.prototype);
+Qubit.prototype.constructor = Qubit;
+
+Object.assign(Qubit, {
+ index: 0,
+ help: function () {
+ return logger.help(this);
+ },
+ constants: {},
+ createConstant: function (key, value) {
+ this[key] = value;
+ this.constants[key] = this[key];
+ Object.freeze(this[key]);
+ },
+ createConstants: function () {
+ if (arguments.length % 2 !== 0) {
+ return logger.error(
+ "Q attempted to create constants with invalid (KEY, VALUE) pairs."
+ );
+ }
+ for (let i = 0; i < arguments.length; i += 2) {
+ this.createConstant(arguments[i], arguments[i + 1]);
+ }
+ },
+
+ findBy: function (key, value) {
+ return Object.values(Qubit.constants).find(function (item) {
+ if (typeof value === "string" && typeof item[key] === "string") {
+ return value.toLowerCase() === item[key].toLowerCase();
+ }
+ return value === item[key];
+ });
+ },
+ findBySymbol: function (symbol) {
+ return Qubit.findBy("symbol", symbol);
+ },
+ findByName: function (name) {
+ return Qubit.findBy("name", name);
+ },
+ findByBeta: function (beta) {
+ if (beta instanceof ComplexNumber === false) {
+ beta = new ComplexNumber(beta);
+ }
+ return Object.values(Qubit.constants).find(function (qubit) {
+ return qubit.beta.isEqualTo(beta);
+ });
+ },
+ areEqual: function (qubit0, qubit1) {
+ return (
+ qubit0.alpha.isEqualTo(qubit1.alpha) && qubit0.beta.isEqualTo(qubit1.beta)
+ );
+ },
+ collapse: function (qubit) {
+ const alpha2 = Math.pow(qubit.alpha.absolute(), 2),
+ beta2 = Math.pow(qubit.beta.absolute(), 2),
+ randomNumberRange = Math.pow(2, 32) - 1,
+ randomNumber = new Uint32Array(1);
+
+ // console.log( 'alpha^2', alpha2 )
+ // console.log( 'beta^2', beta2 )
+ window.crypto.getRandomValues(randomNumber);
+ const randomNumberNormalized = randomNumber / randomNumberRange;
+ if (randomNumberNormalized <= alpha2) {
+ return new Qubit(1, 0);
+ } else return new Qubit(0, 1);
+ },
+ applyGate: function (qubit, gate, ...args) {
+ //TODO test...currently you're updating the gate's matrix property rather than returning a separate instance of the matrix.
+ //this is okay if "gate" is not one of the constants. otherwise, it's bad.
+ `
+ This is means of inverting what comes first:
+ the Gate or the Qubit?
+ If the Gate only operates on a single qubit,
+ then it doesn’t matter and we can do this:
+ `;
+
+ if (gate instanceof Gate === false)
+ return logger.error(
+ `Qubit attempted to apply something that was not a gate to this qubit #${qubit.index}.`
+ );
+ if (gate == Gate.findBy(gate.symbol))
+ return logger.error(`Qubit attempted to apply a reference to the gate constant ${gate.symbol} rather than
+ a copy. This is disallowed.`);
+ else {
+ gate.updateMatrix$(...args);
+ return new Qubit(gate.matrix.multiply(qubit));
+ }
+ },
+ toText: function (qubit) {
+ //return `|${qubit.beta.toText()}⟩`
+ return qubit.alpha.toText() + "\n" + qubit.beta.toText();
+ },
+ toStateVectorText: function (qubit) {
+ return `|${qubit.beta.toText()}⟩`;
+ },
+ toStateVectorHtml: function (qubit) {
+ return `${qubit.beta.toText()} `;
+ },
+
+ // This code was a pain in the ass to figure out.
+ // I’m not fluent in trigonometry
+ // and none of the quantum primers actually lay out
+ // how to convert arbitrary qubit states
+ // to Bloch Sphere representation.
+ // Oh, they provide equivalencies for specific states, sure.
+ // I hope this is useful to you
+ // unless you are porting this to a terrible language
+ // like C# or Java or something ;)
+
+ toBlochSphere: function (qubit) {
+ `
+ Based on this qubit’s state return the
+ Polar angle θ (theta),
+ azimuth angle ϕ (phi),
+ Bloch vector,
+ corrected surface coordinate.
- return this.copy( options, true )
- },
+ https://en.wikipedia.org/wiki/Bloch_sphere
+ `;
+
+ // Polar angle θ (theta).
+
+ const theta = ComplexNumber.arcCosine(qubit.alpha).multiply(2);
+ if (isNaN(theta.real)) theta.real = 0;
+ if (isNaN(theta.imaginary)) theta.imaginary = 0;
+
+ // Azimuth angle ϕ (phi).
+
+ const phi = ComplexNumber.log(
+ qubit.beta.divide(ComplexNumber.sine(theta.divide(2)))
+ ).divide(ComplexNumber.I);
+ if (isNaN(phi.real)) phi.real = 0;
+ if (isNaN(phi.imaginary)) phi.imaginary = 0;
+
+ // Bloch vector.
+
+ const vector = {
+ x: ComplexNumber.sine(theta).multiply(ComplexNumber.cosine(phi)).real,
+ y: ComplexNumber.sine(theta).multiply(ComplexNumber.sine(phi)).real,
+ z: ComplexNumber.cosine(theta).real,
+ };
+
+ // Bloch vector’s axes are wonked.
+ // Let’s “correct” them for use with Three.js, etc.
+
+ const position = {
+ x: vector.y,
+ y: vector.z,
+ z: vector.x,
+ };
+
+ return {
+ // Wow does this make tweening easier down the road.
+
+ alphaReal: qubit.alpha.real,
+ alphaImaginary: qubit.alpha.imaginary,
+ betaReal: qubit.beta.real,
+ betaImaginary: qubit.beta.imaginary,
+
+ // Ummm... I’m only returnig the REAL portions. Please forgive me!
+
+ theta: theta.real,
+ phi: phi.real,
+ vector, // Wonked YZX vector for maths because maths.
+ position, // Un-wonked XYZ for use by actual 3D engines.
+ };
+ },
+ fromBlochVector: function (x, y, z) {
+ //basically from a Pauli Rotation
+ },
+});
+
+Qubit.createConstants(
+ // Opposing pairs:
+ // |H⟩ and |V⟩
+ // |D⟩ and |A⟩
+ // |R⟩ and |L⟩
+
+ "HORIZONTAL",
+ new Qubit(1, 0, "H", "Horizontal"), // ZERO.
+ "VERTICAL",
+ new Qubit(0, 1, "V", "Vertical"), // ONE.
+ "DIAGONAL",
+ new Qubit(Math.SQRT1_2, Math.SQRT1_2, "D", "Diagonal"),
+ "ANTI_DIAGONAL",
+ new Qubit(Math.SQRT1_2, -Math.SQRT1_2, "A", "Anti-diagonal"),
+ "RIGHT_HAND_CIRCULAR_POLARIZED",
+ new Qubit(
+ Math.SQRT1_2,
+ new ComplexNumber(0, -Math.SQRT1_2),
+ "R",
+ "Right-hand Circular Polarized"
+ ), // RHCP
+ "LEFT_HAND_CIRCULAR_POLARIZED",
+ new Qubit(
+ Math.SQRT1_2,
+ new ComplexNumber(0, Math.SQRT1_2),
+ "L",
+ "Left-hand Circular Polarized"
+ ) // LHCP
+);
+
+Object.assign(Qubit.prototype, {
+ copy$: function (matrix) {
+ if (Matrix.isMatrixLike(matrix) !== true)
+ return logger.error(
+ `Qubit attempted to copy something that was not a matrix in this qubit #${qubit.index}.`,
+ this
+ );
+
+ if (Matrix.haveEqualDimensions(matrix, this) !== true)
+ return logger.error(
+ `Qubit cannot copy matrix#${matrix.index} of dimensions ${matrix.columns.length}x${matrix.rows.length} in to this qubit #${this.index} of dimensions ${this.columns.length}x${this.rows.length} because their dimensions do not match.`,
+ this
+ );
+
+ const that = this;
+ matrix.rows.forEach(function (row, r) {
+ row.forEach(function (n, c) {
+ that.rows[r][c] = n;
+ });
+ });
+ this.dirac = matrix.dirac;
+ return this;
+ },
+ clone: function () {
+ return new Qubit(this.alpha, this.beta);
+ },
+ isEqualTo: function (otherQubit) {
+ return Qubit.areEqual(this, otherQubit); // Returns a Boolean, breaks function chaining!
+ },
+ collapse: function () {
+ return Qubit.collapse(this);
+ },
+ applyGate: function (gate, ...args) {
+ return Qubit.applyGate(this, gate, ...args);
+ },
+ toText: function () {
+ return Qubit.toText(this); // Returns a String, breaks function chaining!
+ },
+ toStateVectorText: function () {
+ return Qubit.toStateVectorText(this); // Returns a String, breaks function chaining!
+ },
+ toStateVectorHtml: function () {
+ return Qubit.toStateVectorHtml(this); // Returns a String, breaks function chaining!
+ },
+ toBlochSphere: function () {
+ return Qubit.toBlochSphere(this); // Returns an Object, breaks function chaining!
+ },
+ collapse$: function () {
+ return this.copy$(Qubit.collapse(this));
+ },
+ applyGate$: function (gate) {
+ return this.copy$(Qubit.applyGate(this, gate));
+ },
+});
+
+module.exports = { Qubit };
+
+},{"./Logging":3,"./Misc":5,"./Q-ComplexNumber":7,"./Q-Gate":8,"./Q-Matrix":10}],12:[function(require,module,exports){
+// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+const misc = require('./Misc');
+const mathf = require('./Math-Functions');
+const {Circuit} = require('./Q-Circuit');
+Q = function () {
+ // Did we send arguments of the form
+ // ( bandwidth, timewidth )?
+ if (
+ arguments.length === 2 &&
+ Array.from(arguments).every(function (argument) {
+ return mathf.isUsefulInteger(argument);
+ })
+ ) {
+ return new Circuit(arguments[0], arguments[1]);
+ }
+ // Otherwise assume we are creating a circuit
+ // from a text block.
- /*
+ return Circuit.fromText(arguments[0]);
+};
+console.log(`
- If covers all moments for 1 or more qubits then
- 1. go through each moment and remove those qubits
- 2. remove hanging operations. (right?? don’t want them?)
+ QQQQQQ
+QQ QQ
+QQ QQ
+QQ QQ
+QQ QQ QQ
+QQ QQ
+ QQQQ ${misc.constants.REVISION}
+https://quantumjavascript.app
- */
- spliceCut$: function( options ){
- let {
+`);
- qubitFirstIndex,
- qubitRange,
- qubitLastIndex,
- momentFirstIndex,
- momentRange,
- momentLastIndex
+module.exports = {Q};
- } = this.determineRanges( options )
+},{"./Math-Functions":4,"./Misc":5,"./Q-Circuit":6}],13:[function(require,module,exports){
+const logger = require('./Logging');
+const misc = require('./Misc');
+const mathf = require('./Math-Functions');
+const {ComplexNumber} = require('./Q-ComplexNumber');
+const {Gate} = require('./Q-Gate');
+const {Qubit} = require('./Q-Qubit');
+const {Matrix} = require('./Q-Matrix');
+const {History} = require('./Q-History');
+const {Circuit} = require('./Q-Circuit');
+const {Q} = require('./Q.js');
- // Only three options are valid:
- // 1. Selection area covers ALL qubits for a series of moments.
- // 2. Selection area covers ALL moments for a seriies of qubits.
- // 3. Both of the above (splice the entire circuit).
+module.exports = {logger, misc, mathf, ComplexNumber, Matrix, Gate, Qubit, History, Circuit, Q};
- if( qubitRange !== this.bandwidth &&
- momentRange !== this.timewidth ){
+},{"./Logging":3,"./Math-Functions":4,"./Misc":5,"./Q-Circuit":6,"./Q-ComplexNumber":7,"./Q-Gate":8,"./Q-History":9,"./Q-Matrix":10,"./Q-Qubit":11,"./Q.js":12}],14:[function(require,module,exports){
- return Q.error( `Q.Circuit attempted to splice circuit #${this.index} by an area that did not include all qubits _or_ all moments.` )
- }
+// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
- // If the selection area covers all qubits for 1 or more moments
- // then splice the moments array.
-
- if( qubitRange === this.bandwidth ){
+const {Qubit} = require('quantum-js-util');
+BlochSphere = function( onValueChange ){
- // We cannot use Array.prototype.splice() for this
- // because we need a DEEP copy of the array
- // and splice() will only make a shallow copy.
-
- this.moments = this.moments.reduce( function( accumulator, moment, m ){
+ Object.assign( this, {
- if( m < momentFirstIndex - 1 || m >= momentLastIndex - 1 ) accumulator.push( moment )
- return accumulator
-
- }, [])
- this.timewidth -= momentRange
+ isRotating: false,
+ radius: 1,
+ radiusSafe: 1.01,
+ axesLineWidth: 0.01,
+ arcLineWidth: 0.015,
+ state: Qubit.LEFT_HAND_CIRCULAR_POLARIZED.toBlochSphere(),
+ target: Qubit.HORIZONTAL.toBlochSphere(),
+ group: new THREE.Group(),
+ onValueChange
+ })
- //@@ And how do we implement splicePaste$() here?
- }
+ // Create the surface of the Bloch sphere.
- // If the selection area covers all moments for 1 or more qubits
- // then iterate over each moment and remove those qubits.
-
- if( momentRange === this.timewidth ){
+ const surface = new THREE.Mesh(
+ new THREE.SphereGeometry( this.radius, 64, 64 ),
+ new THREE.MeshPhongMaterial({
- // First, let’s splice the inputs array.
+ side: THREE.FrontSide,
+ map: BlochSphere.makeSurface(),
+ transparent: true,
+ opacity: 0.97
+ })
+ )
+ surface.receiveShadow = true
+ this.group.add( surface )
- this.inputs.splice( qubitFirstIndex, qubitRange )
- //@@ this.inputs.splice( qubitFirstIndex, qubitRange, qubitsToPaste?? )
-
- // Now we can make the proper adjustments
- // to each of our moments.
- this.moments = this.moments.map( function( operations ){
-
- // Remove operations that pertain to the removed qubits.
- // Renumber the remaining operations’ qubitIndices.
-
- return operations.reduce( function( accumulator, operation ){
+ // Create the X, Y, and Z axis lines.
- if( operation.qubitIndices.every( function( index ){
+ const
+ xAxis = new THREE.Mesh(
- return index < qubitFirstIndex || index >= qubitLastIndex
-
- })) accumulator.push( operation )
- return accumulator
-
- }, [])
- .map( function( operation ){
+ new THREE.BoxGeometry( this.axesLineWidth, this.axesLineWidth, this.radius * 2.5 ),
+ new THREE.MeshBasicMaterial({ color: BlochSphere.xAxisColor })
+ ),
+ yAxis = new THREE.Mesh(
- operation.qubitIndices = operation.qubitIndices.map( function( index ){
+ new THREE.BoxGeometry( this.radius * 2.5, this.axesLineWidth, this.axesLineWidth ),
+ new THREE.MeshBasicMaterial({ color: BlochSphere.yAxisColor })
+ ),
+ zAxis = new THREE.Mesh(
- return index >= qubitLastIndex ? index - qubitRange : index
- })
- return operation
- })
- })
- this.bandwidth -= qubitRange
- }
-
+ new THREE.BoxGeometry( this.axesLineWidth, this.radius * 2.5, this.axesLineWidth ),
+ new THREE.MeshBasicMaterial({ color: BlochSphere.zAxisColor })
+ )
- // Final clean up.
+ this.group.add( xAxis, yAxis, zAxis )
- this.removeHangingOperations$()
- this.fillEmptyOperations$()
-
- return this// Or should we return the cut area?!
- },
- splicePaste$: function(){
+ // Create X, Y, and Z arrow heads,
+ // indicating positive directions for all three.
+ const
+ arrowLength = 0.101,// I know, weird, right?
+ arrowHeadLength = 0.1,
+ arrowHeadWidth = 0.1
+
+ this.group.add( new THREE.ArrowHelper(
+
+ new THREE.Vector3( 0, 0, 1.00 ),
+ new THREE.Vector3( 0, 0, 1.25 ),
+ arrowLength,
+ BlochSphere.xAxisColor,// Red
+ arrowHeadLength,
+ arrowHeadWidth
+ ))
+ this.group.add( new THREE.ArrowHelper(
+
+ new THREE.Vector3( 1.00, 0, 0 ),
+ new THREE.Vector3( 1.25, 0, 0 ),
+ arrowLength,
+ BlochSphere.yAxisColor,// Green
+ arrowHeadLength,
+ arrowHeadWidth
+ ))
+ this.group.add( new THREE.ArrowHelper(
+
+ new THREE.Vector3( 0, 1.00, 0 ),
+ new THREE.Vector3( 0, 1.25, 0 ),
+ arrowLength,
+ BlochSphere.zAxisColor,// Blue
+ arrowHeadLength,
+ arrowHeadWidth
+ ))
- },
-
+ // Create the X, Y, and Z axis labels.
+
+ const
+ axesLabelStyle = {
+ width: 128,
+ height: 128,
+ fillStyle: BlochSphere.vectorColor,//'#505962',
+ font: 'bold italic 64px Georgia, "Times New Roman", serif'
+ },
+ xAxisLabel = new THREE.Sprite(
+ new THREE.SpriteMaterial({
- // This is where “hanging operations” get interesting!
- // when you paste one circuit in to another
- // and that clipboard circuit has hanging operations
- // those can find a home in the circuit its being pasted in to!
+ map: Object.assign( SurfaceText( axesLabelStyle ))
+ })
+ ),
+ yAxisLabel = new THREE.Sprite(
+ new THREE.SpriteMaterial({
- paste$: function( other, atMoment = 0, atQubit = 0, shouldClean = true ){
+ map: Object.assign( SurfaceText( axesLabelStyle ))
+ })
+ ),
+ zAxisLabel = new THREE.Sprite(
- const scope = this
- this.timewidth = Math.max( this.timewidth, atMoment + other.timewidth )
- this.bandwidth = Math.max( this.bandwidth, atQubit + other.bandwidth )
- this.ensureMomentsAreReady$()
- this.fillEmptyOperations$()
- other.moments.forEach( function( moment, m ){
+ new THREE.SpriteMaterial({
- moment.forEach( function( operation ){
+ map: Object.assign( SurfaceText( axesLabelStyle ))
+ })
+ )
- //console.log( 'past over w this:', m + atMoment, operation )
+ xAxisLabel.material.map.print( 'x' )
+ xAxisLabel.position.set( 0, 0, 1.45 )
+ xAxisLabel.scale.set( 0.25, 0.25, 0.25 )
+ xAxis.add( xAxisLabel )
- scope.set$(
+ yAxisLabel.material.map.print( 'y' )
+ yAxisLabel.position.set( 1.45, 0, 0 )
+ yAxisLabel.scale.set( 0.25, 0.25, 0.25 )
+ yAxis.add( yAxisLabel )
- operation.gate,
- m + atMoment + 1,
- operation.qubitIndices.map( function( qubitIndex ){
+ zAxisLabel.material.map.print( 'z' )
+ zAxisLabel.position.set( 0, 1.45, 0 )
+ zAxisLabel.scale.set( 0.25, 0.25, 0.25 )
+ zAxis.add( zAxisLabel )
- return qubitIndex + atQubit
- })
- )
- })
- })
- if( shouldClean ) this.removeHangingOperations$()
- this.fillEmptyOperations$()
- return this
- },
- pasteInsert$: function( other, atMoment, atQubit ){
- // if( other.alphandwidth !== this.bandwidth &&
- // other.timewidth !== this.timewidth ) return Q.error( 'Q.Circuit attempted to pasteInsert Circuit A', other, 'in to circuit B', this, 'but neither their bandwidth or timewidth matches.' )
+ this.blochColor = new THREE.Color()
-
+ // Create the line from the sphere’s origin
+ // out to where the Bloch vector intersects
+ // with the sphere’s surface.
- if( shouldClean ) this.removeHangingOperations$()
- this.fillEmptyOperations$()
- return this
+ this.blochVector = new THREE.Mesh(
- },
- expand$: function(){
+ new THREE.BoxGeometry( 0.04, 0.04, this.radius ),
+ new THREE.MeshBasicMaterial({ color: BlochSphere.vectorColor })
+ )
+ this.blochVector.geometry.translate( 0, 0, 0.5 )
+ this.group.add( this.blochVector )
- // expand either bandwidth or timewidth, fill w identity
+ // Create the cone that indicates the Bloch vector
+ // and points to where that vectors
+ // intersects with the surface of the sphere.
- this.fillEmptyOperations$()
- return thiis
- },
+ this.blochPointer = new THREE.Mesh(
+ new THREE.CylinderBufferGeometry( 0, 0.5, 1, 32, 1 ),
+ new THREE.MeshPhongMaterial({ color: BlochSphere.vectorColor })
+ )
+ this.blochPointer.geometry.translate( 0, -0.5, 0 )
+ this.blochPointer.geometry.rotateX( Math.PI / 2 )
+ this.blochPointer.geometry.scale( 0.2, 0.2, 0.2 )
+ this.blochPointer.lookAt( new THREE.Vector3() )
+ this.blochPointer.receiveShadow = true
+ this.blochPointer.castShadow = true
+ this.group.add( this.blochPointer )
+ // Create the Theta ring that will belt the sphere.
+ const
+ arcR = this.radiusSafe * Math.sin( Math.PI / 2 ),
+ arcH = this.radiusSafe * Math.cos( Math.PI / 2 ),
+ thetaGeometry = BlochSphere.createLatitudeArc( arcR, 128, Math.PI / 2, Math.PI * 2 ),
+ thetaLine = new MeshLine(),
+ thetaPhiMaterial = new MeshLineMaterial({
+
+ color: BlochSphere.thetaPhiColor,//0x505962,
+ lineWidth: this.arcLineWidth * 3,
+ sizeAttenuation: true
+ })
+ thetaGeometry.rotateX( Math.PI / 2 )
+ thetaGeometry.rotateY( Math.PI / 2 )
+ thetaGeometry.translate( 0, arcH, 0 )
+ thetaLine.setGeometry( thetaGeometry )
+ this.thetaMesh = new THREE.Mesh(
- trim$: function( options ){
+ thetaLine.geometry,
+ thetaPhiMaterial
+ )
+ this.group.add( this.thetaMesh )
- `
- Edit this circuit by trimming off moments, qubits, or both.
- We could have implemented trim$() as a wrapper around copy$(),
- similar to how cut$ is a wrapper around copy$().
- But this operates on the existing circuit
- instead of returning a new one and returning that.
- `
- let {
+ // Create the Phi arc that will draw from the north pole
+ // down to wherever the Theta arc rests.
- qubitFirstIndex,
- qubitRange,
- qubitLastIndex,
- momentFirstIndex,
- momentRange,
- momentLastIndex
+ this.phiGeometry = BlochSphere.createLongitudeArc( this.radiusSafe, 64, 0, Math.PI * 2 ),
+ this.phiLine = new MeshLine()
+ this.phiLine.setGeometry( this.phiGeometry )
+ this.phiMesh = new THREE.Mesh(
- } = this.determineRanges( options )
+ this.phiLine.geometry,
+ thetaPhiMaterial
+ )
+ this.group.add( this.phiMesh )
- // First, trim the moments down to desired size.
- this.moments = this.moments.slice( momentFirstIndex, momentLastIndex )
- this.timewidth = momentRange
+ // Time to put plans to action.
- // Then, trim the bandwidth down.
+ BlochSphere.prototype.setTargetState.call( this )
+}
- this.inputs = this.inputs.slice( qubitFirstIndex, qubitLastIndex )
- this.bandwidth = qubitRange
- // Finally, remove all gates where
- // gate’s qubit indices contain an index < qubitFirstIndex,
- // gate’s qubit indices contain an index > qubitLastIndex,
- // and fill those holes with Identity gates.
-
- this.removeHangingOperations$()
- this.fillEmptyOperations$()
- return this
- }
-})
+ ////////////////
+ // //
+ // Static //
+ // //
+////////////////
+Object.assign( BlochSphere, {
+ xAxisColor: 0x333333,// Was 0xCF1717 (red)
+ yAxisColor: 0x333333,// Was 0x59A112 (green)
+ zAxisColor: 0x333333,// Was 0x0F66BD (blue)
+ vectorColor: 0xFFFFFF,// Was 0xF2B90D (yellow)
+ thetaPhiColor: 0x333333,// Was 0xF2B90D (yellow)
-// Against my predilection for verbose clarity...
-// I offer you super short convenience methods
-// that do NOT use the $ suffix to delcare they are destructive.
-// Don’t shoot your foot off.
+ // It’s important that we build the texture
+ // right here and now, rather than load an image.
+ // Why? Because if we load a pre-existing image
+ // we run into CORS problems using file:/// !
-Object.entries( Q.Gate.constants ).forEach( function( entry ){
+ makeSurface: function(){
- const
- gateConstantName = entry[ 0 ],
- gate = entry[ 1 ],
- set$ = function( momentIndex, registerIndexOrIndices ){
+ const
+ width = 2048,
+ height = width / 2
- this.set$( gate, momentIndex, registerIndexOrIndices )
- return this
- }
- Q.Circuit.prototype[ gateConstantName ] = set$
- Q.Circuit.prototype[ gate.symbol ] = set$
- Q.Circuit.prototype[ gate.symbol.toLowerCase() ] = set$
-})
+ const canvas = document.createElement( 'canvas' )
+ canvas.width = width
+ canvas.height = height
+
+ const context = canvas.getContext( '2d' )
+ context.fillStyle = 'hsl( 210, 20%, 100% )'
+ context.fillRect( 0, 0, width, height )
+ // Create the base hue gradient for our texture.
-/*
-const bells = [
+ const
+ hueGradient = context.createLinearGradient( 0, height / 2, width, height / 2 ),
+ hueSteps = 180,
+ huesPerStep = 360 / hueSteps
+ for( let i = 0; i <= hueSteps; i ++ ){
- // Verbose without shortcuts.
+ hueGradient.addColorStop( i / hueSteps, 'hsl( '+ ( i * huesPerStep - 90 ) +', 100%, 50% )' )
+ }
+ context.fillStyle = hueGradient
+ context.fillRect( 0, 0, width, height )
- new Q.Circuit( 2, 2 )
- .set$( Q.Gate.HADAMARD, 1, [ 1 ])
- .set$( Q.Gate.PAULI_X, 2, [ 1 , 2 ]),
- new Q.Circuit( 2, 2 )
- .set$( Q.Gate.HADAMARD, 1, 1 )
- .set$( Q.Gate.PAULI_X, 2, [ 1 , 2 ]),
+ // For both the northern gradient (to white)
+ // and the southern gradient (to black)
+ // we’ll leave a thin band of full saturation
+ // near the equator.
+ const whiteGradient = context.createLinearGradient( width / 2, 0, width / 2, height / 2 )
+ whiteGradient.addColorStop( 0.000, 'hsla( 0, 0%, 100%, 1 )' )
+ whiteGradient.addColorStop( 0.125, 'hsla( 0, 0%, 100%, 1 )' )
+ whiteGradient.addColorStop( 0.875, 'hsla( 0, 0%, 100%, 0 )' )
+ context.fillStyle = whiteGradient
+ context.fillRect( 0, 0, width, height / 2 )
- // Uses Q.Gate.findBySymbol() to lookup gates.
+ const blackGradient = context.createLinearGradient( width / 2, height / 2, width / 2, height )
+ blackGradient.addColorStop( 0.125, 'hsla( 0, 0%, 0%, 0 )' )
+ blackGradient.addColorStop( 0.875, 'hsla( 0, 0%, 0%, 1 )' )
+ blackGradient.addColorStop( 1.000, 'hsla( 0, 0%, 0%, 1 )' )
+ context.fillStyle = blackGradient
+ context.fillRect( 0, height / 2, width, height )
- new Q.Circuit( 2, 2 )
- .set$( 'H', 1, [ 1 ])
- .set$( 'X', 2, [ 1 , 2 ]),
- new Q.Circuit( 2, 2 )
- .set$( 'H', 1, 1 )
- .set$( 'X', 2, [ 1 , 2 ]),
+ // Create lines of latitude and longitude.
+ // Note this is an inverse Mercatur projection ;)
+ context.fillStyle = 'hsla( 0, 0%, 0%, 0.2 )'
+ const yStep = height / 16
+ for( let y = 0; y <= height; y += yStep ){
- // Convenience gate functions -- constant name.
+ context.fillRect( 0, y, width, 1 )
+ }
+ const xStep = width / 16
+ for( let x = 0; x <= width; x += xStep ){
- new Q.Circuit( 2, 2 )
- .HADAMARD( 1, [ 1 ])
- .PAULI_X( 2, [ 1, 2 ]),
+ context.fillRect( x, 0, 1, height )
+ }
- new Q.Circuit( 2, 2 )
- .HADAMARD( 1, 1 )
- .PAULI_X( 2, [ 1, 2 ]),
+ // Prepare the THREE texture and return it
+ // so we can use it as a material map.
- // Convenience gate functions -- uppercase symbol.
+ const texture = new THREE.CanvasTexture( canvas )
+ texture.needsUpdate = true
+ return texture
+ },
- new Q.Circuit( 2, 2 )
- .H( 1, [ 1 ])
- .X( 2, [ 1, 2 ]),
- new Q.Circuit( 2, 2 )
- .H( 1, 1 )
- .X( 2, [ 1, 2 ]),
- // Convenience gate functions -- lowercase symbol.
+ createLongitudeArc: function( radius, segments, thetaStart, thetaLength ){
- new Q.Circuit( 2, 2 )
- .h( 1, [ 1 ])
- .x( 2, [ 1, 2 ]),
+ const geometry = new THREE.CircleGeometry( radius, segments, thetaStart, thetaLength )
+ geometry.vertices.shift()
+
- new Q.Circuit( 2, 2 )// Perhaps the closest to Braket style.
- .h( 1, 1 )
- .x( 2, [ 1, 2 ]),
+ // This is NOT NORMALLY necessary
+ // because we expect this to only be
+ // between PI/2 and PI*2
+ // (so the length is only Math.PI instead of PI*2).
+ if( thetaLength >= Math.PI * 2 ){
- // Q function -- bandwidth / timewidth arguments.
+ geometry.vertices.push( geometry.vertices[ 0 ].clone() )
+ }
+ return geometry
+ },
+ createLatitudeArc: function( radius, segments, phiStart, phiLength ){
- Q( 2, 2 )
- .h( 1, [ 1 ])
- .x( 2, [ 1, 2 ]),
+ const geometry = new THREE.CircleGeometry( radius, segments, phiStart, phiLength )
+ geometry.vertices.shift()
+ if( phiLength >= 2 * Math.PI ){
- Q( 2, 2 )
- .h( 1, 1 )
- .x( 2, [ 1, 2 ]),
+ geometry.vertices.push( geometry.vertices[ 0 ].clone() )
+ }
+ return geometry
+ },
+ createQuadSphere: function( options ){
+ let {
- // Q function -- text block argument
- // with operation symbols
- // and operation component IDs.
+ radius,
+ phiStart,
+ phiLength,
+ thetaStart,
+ thetaLength,
+ latitudeLinesTotal,
+ longitudeLinesTotal,
+ latitudeLineSegments,
+ longitudeLineSegments,
+ latitudeLinesAttributes,
+ longitudeLinesAttributes
- Q`
- H-X.0#0
- I-X.0#1`,
+ } = options
-
- // Q function -- text block argument
- // using only component IDs
- // (ie. no operation symbols)
- // because the operation that the
- // components should belong to is NOT ambiguous.
-
- Q`
- H-X#0
- I-X#1`,
+ if( typeof radius !== 'number' ) radius = 1
+ if( typeof phiStart !== 'number' ) phiStart = Math.PI / 2
+ if( typeof phiLength !== 'number' ) phiLength = Math.PI * 2
+ if( typeof thetaStart !== 'number' ) thetaStart = 0
+ if( typeof thetaLength !== 'number' ) thetaLength = Math.PI
+ if( typeof latitudeLinesTotal !== 'number' ) latitudeLinesTotal = 16
+ if( typeof longitudeLinesTotal !== 'number' ) longitudeLinesTotal = 16
+ if( typeof latitudeLineSegments !== 'number' ) latitudeLineSegments = 64
+ if( typeof longitudeLineSegments !== 'number' ) longitudeLineSegments = 64
+ if( typeof latitudeLinesAttributes === 'undefined' ) latitudeLinesAttributes = { color: 0xCCCCCC }
+ if( typeof longitudeLinesAttributes === 'undefined' ) longitudeLinesAttributes = { color: 0xCCCCCC }
+ const
+ sphere = new THREE.Group(),
+ latitudeLinesMaterial = new THREE.LineBasicMaterial( latitudeLinesAttributes ),
+ longitudeLinesMaterial = new THREE.LineBasicMaterial( longitudeLinesAttributes )
- // Q function -- text block argument
- // as above, but using only whitespace
- // to partition between moments.
- Q`
- H X#0
- I X#1`
-],
-bellsAreEqual = !!bells.reduce( function( a, b ){
+ // Lines of longitude.
+ // https://en.wikipedia.org/wiki/Longitude
- return a.toText() === b.toText() ? a : NaN
+ for(
+
+ let
+ phiDelta = phiLength / longitudeLinesTotal,
+ phi = phiStart,
+ arc = BlochSphere.createLongitudeArc( radius, longitudeLineSegments, thetaStart + Math.PI / 2, thetaLength );
+ phi < phiStart + phiLength + phiDelta;
+ phi += phiDelta ){
+
+ const geometry = arc.clone()
+ geometry.rotateY( phi )
+ sphere.add( new THREE.Line( geometry, longitudeLinesMaterial ))
+ }
-})
-if( bellsAreEqual ){
- console.log( `\n\nYES. All of ${ bells.length } our “Bell” circuits are equal.\n\n`, bells )
-}
-*/
+ // Lines of latitude.
+ // https://en.wikipedia.org/wiki/Latitude
+ for (
+ let
+ thetaDelta = thetaLength / latitudeLinesTotal,
+ theta = thetaStart;
+ theta < thetaStart + thetaLength;
+ theta += thetaDelta ){
+
+ if( theta === 0 ) continue
+
+ const
+ arcR = radius * Math.sin( theta ),
+ arcH = radius * Math.cos( theta ),
+ geometry = BlochSphere.createLatitudeArc( arcR, latitudeLineSegments, phiStart, phiLength )
+
+ geometry.rotateX( Math.PI / 2 )
+ geometry.rotateY( Math.PI / 2 )
+ geometry.translate( 0, arcH, 0 )
+ sphere.add( new THREE.Line( geometry, latitudeLinesMaterial ))
+ }
+ return sphere
+ }
+})
-Q.Circuit.createConstants(
- 'BELL', Q`
- H X#0
- I X#1
- `,
- // 'GROVER', Q`
- // H X *#0 X#0 I X#0 I I I X#0 I I I X#0 I X H X I *#0
- // H X I X#1 *#0 X#1 *#0 X#0 I I I X#0 X I H X I I I I
- // H X I I I I I X#1 *#0 X#1 *#0 X#1 *#0 X#1 I *#0 X H X I
- // H X *#1 I *#1 I *#1 I *#1 I *#1 I *#1 I I *#1 X H X *#1
- // `
+ ///////////////
+ // //
+ // Proto //
+ // //
+///////////////
- //https://docs.microsoft.com/en-us/quantum/concepts/circuits?view=qsharp-preview
- // 'TELEPORT', Q.(`
-
- // I-I--H-M---v
- // H-C0-I-M-v-v
- // I-C1-I-I-X-Z-
- // `)
-)
+Object.assign( BlochSphere.prototype, {
+ update: function(){
+ if( this.isRotating ) this.group.rotation.y += Math.PI / 4096
+ },
+ setTargetState: function( target ){
+
+ if( target === undefined ) target = Qubit.HORIZONTAL.toBlochSphere()
+ // Always take the shortest path around
+ // even if it crosses the 0˚ / 360˚ boundary,
+ // ie. between Anti-Diagonal (-90˚) and
+ // Right0-and circular polarized (180˚).
+ const
+ rangeHalf = Math.PI,
+ distance = this.state.phi - target.phi
+ if( Math.abs( distance ) > rangeHalf ){
+ this.state.phi += Math.sign( distance ) * rangeHalf * -2
+ }
+ // Cheap hack to test if we need to update values
+ // from within the updateBlochVector method.
+ Object.assign( this.target, target )
+
+ // Create the tween.
+ window.tween = new TWEEN.Tween( this.state )
+ .to( target, 1000 )
+ .easing( TWEEN.Easing.Quadratic.InOut )
+ .onUpdate( this.updateBlochVector.bind( this ))
+ .start()
+ },
+ updateBlochVector: function( state ){
+ // Move the big-ass surface pointer.
+ if( state.theta !== this.target.theta ||
+ state.phi !== this.target.phi ){
-/*
+ this.blochPointer.position.set(
+
+ Math.sin( state.theta ) * Math.sin( state.phi ),
+ Math.cos( state.theta ),
+ Math.sin( state.theta ) * Math.cos( state.phi )
+ )
+ this.blochPointer.lookAt( new THREE.Vector3() )
+ this.blochVector.lookAt( this.blochPointer.getWorldPosition( new THREE.Vector3() ))
-%%HTML
-
-
-
+ // Determine the correct HSL color
+ // based on Phi and Theta.
+ let hue = state.phi * THREE.Math.RAD2DEG
+ if( hue < 0 ) hue = 360 + hue
+ this.blochColor.setHSL(
-%%javascript
-Q.braket( element )
+ hue / 360,
+ 1,
+ 1 - ( state.theta / Math.PI )
+ )
+ this.blochPointer.material.color = this.blochColor
+ this.blochVector.material.color = this.blochColor
+
+ if( state.theta !== this.target.theta ){
+ // Slide the Theta ring from the north pole
+ // down as far south as it needs to go
+ // and scale its radius so it belts the sphere.
+ const thetaScaleSafe = Math.max( state.theta, 0.01 )
+ this.thetaMesh.scale.set(
-*/
+ Math.sin( thetaScaleSafe ),
+ 1,
+ Math.sin( thetaScaleSafe )
+ )
+ this.thetaMesh.position.y = Math.cos( state.theta )
+
+ // Redraw the Phi arc to extend from the north pole
+ // down to only as far as the Theta ring sits.
+ // Then rotate the whole Phi arc about the poles.
+ for(
-//%%javascript
+ let
+ i = 0,
+ limit = this.phiGeometry.vertices.length;
+ i < limit;
+ i ++ ){
+ const gain = i / ( limit - 1 )
+ this.phiGeometry.vertices[ i ].set(
-// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+ Math.sin( state.theta * gain ) * this.radiusSafe,
+ Math.cos( state.theta * gain ) * this.radiusSafe,
+ 0
+ )
+ }
+ this.phiLine.setGeometry( this.phiGeometry )
+ }
+ if( state.phi !== this.target.phi ){
+
+ this.phiMesh.rotation.y = state.phi - Math.PI / 2
+ }
+ if( typeof this.onValueChange === 'function' ) this.onValueChange.call( this )
+ }
+ }
+})
+module.exports = {BlochSphere}
-Q.Circuit.Editor = function( circuit, targetEl ){
+
+},{"quantum-js-util":13}],15:[function(require,module,exports){
+// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+const {Q, Circuit, Gate, logger, misc, mathf } = require('quantum-js-util');
+Editor = function( circuit, targetEl ){
// First order of business,
// we require a valid circuit.
- if( circuit instanceof Q.Circuit !== true ) circuit = new Q.Circuit()
+ if( circuit instanceof Circuit !== true ) circuit = new Circuit()
this.circuit = circuit
- this.index = Q.Circuit.Editor.index ++
+ this.index = Editor.index ++
- // Q.Circuit.Editor is all about the DOM
+ // Editor is all about the DOM
// so we’re going to get some use out of this
// stupid (but convenient) shorthand here.
@@ -5493,6 +5883,8 @@ Q.Circuit.Editor = function( circuit, targetEl ){
return document.createElement( 'div' )
}
+
+
@@ -5584,12 +5976,12 @@ Q.Circuit.Editor = function( circuit, targetEl ){
undoButton.setAttribute( 'title', 'Undo' )
undoButton.setAttribute( 'Q-disabled', 'Q-disabled' )
undoButton.innerHTML = '⟲'
- window.addEventListener( 'Q.History undo is depleted', function( event ){
+ window.addEventListener( 'History undo is depleted', function( event ){
if( event.detail.instance === circuit )
undoButton.setAttribute( 'Q-disabled', 'Q-disabled' )
})
- window.addEventListener( 'Q.History undo is capable', function( event ){
+ window.addEventListener( 'History undo is capable', function( event ){
if( event.detail.instance === circuit )
undoButton.removeAttribute( 'Q-disabled' )
@@ -5606,12 +5998,12 @@ Q.Circuit.Editor = function( circuit, targetEl ){
redoButton.setAttribute( 'title', 'Redo' )
redoButton.setAttribute( 'Q-disabled', 'Q-disabled' )
redoButton.innerHTML = '⟳'
- window.addEventListener( 'Q.History redo is depleted', function( event ){
+ window.addEventListener( 'History redo is depleted', function( event ){
if( event.detail.instance === circuit )
redoButton.setAttribute( 'Q-disabled', 'Q-disabled' )
})
- window.addEventListener( 'Q.History redo is capable', function( event ){
+ window.addEventListener( 'History redo is capable', function( event ){
if( event.detail.instance === circuit )
redoButton.removeAttribute( 'Q-disabled' )
@@ -5651,9 +6043,9 @@ Q.Circuit.Editor = function( circuit, targetEl ){
const boardContainerEl = createDiv()
circuitEl.appendChild( boardContainerEl )
boardContainerEl.classList.add( 'Q-circuit-board-container' )
- //boardContainerEl.addEventListener( 'touchstart', Q.Circuit.Editor.onPointerPress )
+ //boardContainerEl.addEventListener( 'touchstart', Editor.onPointerPress )
boardContainerEl.addEventListener( 'mouseleave', function(){
- Q.Circuit.Editor.unhighlightAll( circuitEl )
+ Editor.unhighlightAll( circuitEl )
})
const boardEl = createDiv()
@@ -5677,7 +6069,7 @@ Q.Circuit.Editor = function( circuit, targetEl ){
rowEl.style.position = 'relative'
rowEl.style.gridRowStart = i + 2
rowEl.style.gridColumnStart = 1
- rowEl.style.gridColumnEnd = Q.Circuit.Editor.momentIndexToGridColumn( circuit.timewidth ) + 1
+ rowEl.style.gridColumnEnd = Editor.momentIndexToGridColumn( circuit.timewidth ) + 1
rowEl.setAttribute( 'register-index', i + 1 )
const wireEl = createDiv()
@@ -5694,7 +6086,7 @@ Q.Circuit.Editor = function( circuit, targetEl ){
const columnEl = createDiv()
backgroundEl.appendChild( columnEl )
columnEl.style.gridRowStart = 2
- columnEl.style.gridRowEnd = Q.Circuit.Editor.registerIndexToGridRow( circuit.bandwidth ) + 1
+ columnEl.style.gridRowEnd = Editor.registerIndexToGridRow( circuit.bandwidth ) + 1
columnEl.style.gridColumnStart = i + 3
columnEl.setAttribute( 'moment-index', i + 1 )
}
@@ -5731,7 +6123,7 @@ Q.Circuit.Editor = function( circuit, targetEl ){
registersymbolEl.classList.add( 'Q-circuit-header', 'Q-circuit-register-label' )
registersymbolEl.setAttribute( 'title', 'Register '+ registerIndex +' of '+ circuit.bandwidth )
registersymbolEl.setAttribute( 'register-index', registerIndex )
- registersymbolEl.style.gridRowStart = Q.Circuit.Editor.registerIndexToGridRow( registerIndex )
+ registersymbolEl.style.gridRowStart = Editor.registerIndexToGridRow( registerIndex )
registersymbolEl.innerText = registerIndex
}
@@ -5742,7 +6134,7 @@ Q.Circuit.Editor = function( circuit, targetEl ){
foregroundEl.appendChild( addRegisterEl )
addRegisterEl.classList.add( 'Q-circuit-header', 'Q-circuit-register-add' )
addRegisterEl.setAttribute( 'title', 'Add register' )
- addRegisterEl.style.gridRowStart = Q.Circuit.Editor.registerIndexToGridRow( circuit.bandwidth + 1 )
+ addRegisterEl.style.gridRowStart = Editor.registerIndexToGridRow( circuit.bandwidth + 1 )
addRegisterEl.innerText = '+'
@@ -5758,7 +6150,7 @@ Q.Circuit.Editor = function( circuit, targetEl ){
momentsymbolEl.classList.add( 'Q-circuit-header', 'Q-circuit-moment-label' )
momentsymbolEl.setAttribute( 'title', 'Moment '+ momentIndex +' of '+ circuit.timewidth )
momentsymbolEl.setAttribute( 'moment-index', momentIndex )
- momentsymbolEl.style.gridColumnStart = Q.Circuit.Editor.momentIndexToGridColumn( momentIndex )
+ momentsymbolEl.style.gridColumnStart = Editor.momentIndexToGridColumn( momentIndex )
momentsymbolEl.innerText = momentIndex
}
@@ -5769,7 +6161,7 @@ Q.Circuit.Editor = function( circuit, targetEl ){
foregroundEl.appendChild( addMomentEl )
addMomentEl.classList.add( 'Q-circuit-header', 'Q-circuit-moment-add' )
addMomentEl.setAttribute( 'title', 'Add moment' )
- addMomentEl.style.gridColumnStart = Q.Circuit.Editor.momentIndexToGridColumn( circuit.timewidth + 1 )
+ addMomentEl.style.gridColumnStart = Editor.momentIndexToGridColumn( circuit.timewidth + 1 )
addMomentEl.innerText = '+'
@@ -5784,7 +6176,7 @@ Q.Circuit.Editor = function( circuit, targetEl ){
inputEl.classList.add( 'Q-circuit-header', 'Q-circuit-input' )
inputEl.setAttribute( 'title', `Qubit #${ rowIndex } starting value` )
inputEl.setAttribute( 'register-index', rowIndex )
- inputEl.style.gridRowStart = Q.Circuit.Editor.registerIndexToGridRow( rowIndex )
+ inputEl.style.gridRowStart = Editor.registerIndexToGridRow( rowIndex )
inputEl.innerText = qubit.beta.toText()
foregroundEl.appendChild( inputEl )
})
@@ -5793,23 +6185,23 @@ Q.Circuit.Editor = function( circuit, targetEl ){
// Add operations.
circuit.operations.forEach( function( operation ){
- Q.Circuit.Editor.set( circuitEl, operation )
+ Editor.set( circuitEl, operation )
})
// Add event listeners.
- circuitEl.addEventListener( 'mousedown', Q.Circuit.Editor.onPointerPress )
- circuitEl.addEventListener( 'touchstart', Q.Circuit.Editor.onPointerPress )
+ circuitEl.addEventListener( 'mousedown', Editor.onPointerPress )
+ circuitEl.addEventListener( 'touchstart', Editor.onPointerPress )
window.addEventListener(
- 'Q.Circuit.set$',
- Q.Circuit.Editor.prototype.onExternalSet.bind( this )
+ 'Circuit.set$',
+ Editor.prototype.onExternalSet.bind( this )
)
window.addEventListener(
- 'Q.Circuit.clear$',
- Q.Circuit.Editor.prototype.onExternalClear.bind( this )
+ 'Circuit.clear$',
+ Editor.prototype.onExternalClear.bind( this )
)
@@ -5830,8 +6222,7 @@ Q.Circuit.Editor = function( circuit, targetEl ){
// that includes how to reference the circuit via code
// and an ASCII diagram for reference.
- Q.log( 0.5,
-
+ logger.warn( 0.5,
`\n\nCreated a DOM interface for $('#${ this.domId }').circuit\n\n`,
circuit.toDiagram(),
'\n\n\n'
@@ -5839,15 +6230,15 @@ Q.Circuit.Editor = function( circuit, targetEl ){
}
-// Augment Q.Circuit to have this functionality.
+// Augment Circuit to have this functionality.
-Q.Circuit.toDom = function( circuit, targetEl ){
+Circuit.toDom = function( circuit, targetEl ){
- return new Q.Circuit.Editor( circuit, targetEl ).domElement
+ return new Editor( circuit, targetEl ).domElement
}
-Q.Circuit.prototype.toDom = function( targetEl ){
+Circuit.prototype.toDom = function( targetEl ){
- return new Q.Circuit.Editor( this, targetEl ).domElement
+ return new Editor( this, targetEl ).domElement
}
@@ -5857,10 +6248,10 @@ Q.Circuit.prototype.toDom = function( targetEl ){
-Object.assign( Q.Circuit.Editor, {
+Object.assign( Editor, {
index: 0,
- help: function(){ return Q.help( this )},
+ help: function(){ return logger.help( this )},
dragEl: null,
gridColumnToMomentIndex: function( gridColumn ){ return +gridColumn - 2 },
momentIndexToGridColumn: function( momentIndex ){ return momentIndex + 2 },
@@ -5876,7 +6267,7 @@ Object.assign( Q.Circuit.Editor, {
// based on our 4rem × 4rem grid setup.
const rem = parseFloat( getComputedStyle( document.documentElement ).fontSize )
- return 1 + Math.floor( p / ( rem * Q.Circuit.Editor.gridSize ))
+ return 1 + Math.floor( p / ( rem * Editor.gridSize ))
},
gridToPoint: function( g ){
@@ -5886,7 +6277,7 @@ Object.assign( Q.Circuit.Editor, {
// and return the minimum point value it contains.
const rem = parseFloat( getComputedStyle( document.documentElement ).fontSize )
- return rem * Q.Circuit.Editor.gridSize * ( g - 1 )
+ return rem * Editor.gridSize * ( g - 1 )
},
getInteractionCoordinates: function( event, pageOrClient ){
@@ -5898,11 +6289,16 @@ Object.assign( Q.Circuit.Editor, {
y: event.changedTouches[ 0 ][ pageOrClient +'Y' ]
}
return {
-
x: event[ pageOrClient +'X' ],
y: event[ pageOrClient +'Y' ]
}
},
+ createNewElement :function(element_type, element_parent, element_css) {
+ element = document.createElement(element_type)
+ if(element_css) element.classList.add(element_css)
+ if(element_parent) element_parent.appendChild( element )
+ return element
+ },
createPalette: function( targetEl ){
if( typeof targetEl === 'string' ) targetEl = document.getElementById( targetEl )
@@ -5916,12 +6312,12 @@ Object.assign( Q.Circuit.Editor, {
}
//ltnln: added missing Braket operations.
- paletteEl.classList.add( 'Q-circuit-palette' )
+ paletteEl.classList.add( 'Q-circuit-palette' );
'H,X,Y,Z,P,Rx,Ry,Rz,U,V,V†,S*,S†,T,T†,00,01,10,√S,iS,XX,XY,YY,ZZ,*'
.split( ',' )
.forEach( function( symbol ){
- const gate = Q.Gate.findBySymbol( symbol )
+ const gate = Gate.findBySymbol( symbol )
const operationEl = document.createElement( 'div' )
paletteEl.appendChild( operationEl )
@@ -5933,7 +6329,7 @@ Object.assign( Q.Circuit.Editor, {
const tileEl = document.createElement( 'div' )
operationEl.appendChild( tileEl )
tileEl.classList.add( 'Q-circuit-operation-tile' )
- if( symbol !== Q.Gate.CURSOR.symbol ) tileEl.innerText = symbol
+ if( symbol !== Gate.CURSOR.symbol ) tileEl.innerText = symbol
;[ 'before', 'after' ].forEach( function( layer ){
@@ -5943,9 +6339,13 @@ Object.assign( Q.Circuit.Editor, {
})
})
- paletteEl.addEventListener( 'mousedown', Q.Circuit.Editor.onPointerPress )
- paletteEl.addEventListener( 'touchstart', Q.Circuit.Editor.onPointerPress )
+ paletteEl.addEventListener( 'mousedown', Editor.onPointerPress )
+ paletteEl.addEventListener( 'touchstart', Editor.onPointerPress )
return paletteEl
+ },
+ toDom: function( circuit, targetEl ){
+
+ return new Editor( circuit, targetEl ).domElement
}
})
@@ -5961,18 +6361,18 @@ Object.assign( Q.Circuit.Editor, {
/////////////////////////
-Q.Circuit.Editor.prototype.onExternalClear = function( event ){
+Editor.prototype.onExternalClear = function( event ){
if( event.detail.circuit === this.circuit ){
- Q.Circuit.Editor.clear( this.domElement, {
+ Editor.clear( this.domElement, {
momentIndex: event.detail.momentIndex,
registerIndices: event.detail.registerIndices
})
}
}
-Q.Circuit.Editor.clear = function( circuitEl, operation ){
+Editor.clear = function( circuitEl, operation ){
const momentIndex = operation.momentIndex
operation.registerIndices.forEach( function( registerIndex ){
@@ -6003,14 +6403,14 @@ Q.Circuit.Editor.clear = function( circuitEl, operation ){
///////////////////////
-Q.Circuit.Editor.prototype.onExternalSet = function( event ){
+Editor.prototype.onExternalSet = function( event ){
if( event.detail.circuit === this.circuit ){
- Q.Circuit.Editor.set( this.domElement, event.detail.operation )
+ Editor.set( this.domElement, event.detail.operation )
}
}
-Q.Circuit.Editor.set = function( circuitEl, operation ){
+Editor.set = function( circuitEl, operation ){
const
backgroundEl = circuitEl.querySelector( '.Q-circuit-board-background' ),
foregroundEl = circuitEl.querySelector( '.Q-circuit-board-foreground' ),
@@ -6029,15 +6429,15 @@ Q.Circuit.Editor.set = function( circuitEl, operation ){
operationEl.setAttribute( 'register-array-index', i )// Where within the registerIndices array is this operations fragment located?
operationEl.setAttribute( 'is-controlled', operation.isControlled )
operationEl.setAttribute( 'title', operation.gate.name )
- operationEl.style.gridColumnStart = Q.Circuit.Editor.momentIndexToGridColumn( operation.momentIndex )
- operationEl.style.gridRowStart = Q.Circuit.Editor.registerIndexToGridRow( registerIndex )
+ operationEl.style.gridColumnStart = Editor.momentIndexToGridColumn( operation.momentIndex )
+ operationEl.style.gridRowStart = Editor.registerIndexToGridRow( registerIndex )
if( operation.gate.has_parameters ) Object.keys(operation.gate.parameters).forEach( element => {
operationEl.setAttribute( element, operation.gate.parameters[element] ) //adds a parameter attribute to the operation!
})
const tileEl = document.createElement( 'div' )
operationEl.appendChild( tileEl )
tileEl.classList.add( 'Q-circuit-operation-tile' )
- if( operation.gate.symbol !== Q.Gate.CURSOR.symbol ) tileEl.innerText = operation.gate.symbol
+ if( operation.gate.symbol !== Gate.CURSOR.symbol ) tileEl.innerText = operation.gate.symbol
// Add operation link wires
@@ -6072,9 +6472,9 @@ Q.Circuit.Editor.set = function( circuitEl, operation ){
containerEl.setAttribute( 'moment-index', operation.momentIndex )
containerEl.setAttribute( 'register-index', registerIndex )
containerEl.classList.add( 'Q-circuit-operation-link-container' )
- containerEl.style.gridRowStart = Q.Circuit.Editor.registerIndexToGridRow( start )
- containerEl.style.gridRowEnd = Q.Circuit.Editor.registerIndexToGridRow( end + 1 )
- containerEl.style.gridColumn = Q.Circuit.Editor.momentIndexToGridColumn( operation.momentIndex )
+ containerEl.style.gridRowStart = Editor.registerIndexToGridRow( start )
+ containerEl.style.gridRowEnd = Editor.registerIndexToGridRow( end + 1 )
+ containerEl.style.gridColumn = Editor.momentIndexToGridColumn( operation.momentIndex )
containerEl.appendChild( linkEl )
linkEl.classList.add( 'Q-circuit-operation-link' )
@@ -6094,7 +6494,7 @@ Q.Circuit.Editor.set = function( circuitEl, operation ){
-Q.Circuit.Editor.isValidControlCandidate = function( circuitEl ){
+Editor.isValidControlCandidate = function( circuitEl ){
const
selectedOperations = Array
@@ -6191,7 +6591,7 @@ Q.Circuit.Editor.isValidControlCandidate = function( circuitEl ){
const gates = selectedOperations.reduce( function( gates, operationEl ){
const gateSymbol = operationEl.getAttribute( 'gate-symbol' )
- if( !Q.isUsefulInteger( gates[ gateSymbol ])) gates[ gateSymbol ] = 1
+ if( !mathf.isUsefulInteger( gates[ gateSymbol ])) gates[ gateSymbol ] = 1
else gates[ gateSymbol ] ++
return gates
@@ -6223,7 +6623,7 @@ Q.Circuit.Editor.isValidControlCandidate = function( circuitEl ){
// and one or more of a regular single gate
// that is NOT already controlled.
- if( gates[ Q.Gate.CURSOR.symbol ] === 1 &&
+ if( gates[ Gate.CURSOR.symbol ] === 1 &&
Object.keys( gates ).length === 2 &&
totalNotControlled === selectedOperations.length ){
@@ -6235,7 +6635,7 @@ Q.Circuit.Editor.isValidControlCandidate = function( circuitEl ){
// but there is one or more of specific gate type
// and at least one of those is already controlled.
- if( gates[ Q.Gate.CURSOR.symbol ] === undefined &&
+ if( gates[ Gate.CURSOR.symbol ] === undefined &&
Object.keys( gates ).length === 1 &&
totalControlled > 0 &&
totalNotControlled > 0 ){
@@ -6248,9 +6648,9 @@ Q.Circuit.Editor.isValidControlCandidate = function( circuitEl ){
return false
}
-Q.Circuit.Editor.createControl = function( circuitEl ){
+Editor.createControl = function( circuitEl ){
- if( Q.Circuit.Editor.isValidControlCandidate( circuitEl ) !== true ) return this
+ if( Editor.isValidControlCandidate( circuitEl ) !== true ) return this
const
@@ -6278,7 +6678,7 @@ Q.Circuit.Editor.createControl = function( circuitEl ){
control = existingControlEl || selectedOperations
.find( function( el ){
- return el.getAttribute( 'gate-symbol' ) === Q.Gate.CURSOR.symbol
+ return el.getAttribute( 'gate-symbol' ) === Gate.CURSOR.symbol
}),
targets = selectedOperations
.reduce( function( targets, el ){
@@ -6318,8 +6718,8 @@ Q.Circuit.Editor.createControl = function( circuitEl ){
// Update our toolbar button states.
- Q.Circuit.Editor.onSelectionChanged( circuitEl )
- Q.Circuit.Editor.onCircuitChanged( circuitEl )
+ Editor.onSelectionChanged( circuitEl )
+ Editor.onCircuitChanged( circuitEl )
return this
}
@@ -6327,7 +6727,7 @@ Q.Circuit.Editor.createControl = function( circuitEl ){
-Q.Circuit.Editor.isValidSwapCandidate = function( circuitEl ){
+Editor.isValidSwapCandidate = function( circuitEl ){
const
selectedOperations = Array
@@ -6344,7 +6744,7 @@ Q.Circuit.Editor.isValidSwapCandidate = function( circuitEl ){
areBothCursors = selectedOperations.every( function( operationEl ){
- return operationEl.getAttribute( 'gate-symbol' ) === Q.Gate.CURSOR.symbol
+ return operationEl.getAttribute( 'gate-symbol' ) === Gate.CURSOR.symbol
})
if( areBothCursors ) return true
@@ -6353,9 +6753,9 @@ Q.Circuit.Editor.isValidSwapCandidate = function( circuitEl ){
return false
}
-Q.Circuit.Editor.createSwap = function( circuitEl ){
+Editor.createSwap = function( circuitEl ){
- if( Q.Circuit.Editor.isValidSwapCandidate( circuitEl ) !== true ) return this
+ if( Editor.isValidSwapCandidate( circuitEl ) !== true ) return this
const
selectedOperations = Array
@@ -6384,7 +6784,7 @@ Q.Circuit.Editor.createSwap = function( circuitEl ){
})
circuit.set$(
- Q.Gate.SWAP,
+ Gate.SWAP,
momentIndex,
registerIndices
)
@@ -6392,8 +6792,8 @@ Q.Circuit.Editor.createSwap = function( circuitEl ){
// Update our toolbar button states.
- Q.Circuit.Editor.onSelectionChanged( circuitEl )
- Q.Circuit.Editor.onCircuitChanged( circuitEl )
+ Editor.onSelectionChanged( circuitEl )
+ Editor.onCircuitChanged( circuitEl )
return this
}
@@ -6401,23 +6801,23 @@ Q.Circuit.Editor.createSwap = function( circuitEl ){
-Q.Circuit.Editor.onSelectionChanged = function( circuitEl ){
+Editor.onSelectionChanged = function( circuitEl ){
const controlButtonEl = circuitEl.querySelector( '.Q-circuit-toggle-control' )
- if( Q.Circuit.Editor.isValidControlCandidate( circuitEl )){
+ if( Editor.isValidControlCandidate( circuitEl )){
controlButtonEl.removeAttribute( 'Q-disabled' )
}
else controlButtonEl.setAttribute( 'Q-disabled', true )
const swapButtonEl = circuitEl.querySelector( '.Q-circuit-toggle-swap' )
- if( Q.Circuit.Editor.isValidSwapCandidate( circuitEl )){
+ if( Editor.isValidSwapCandidate( circuitEl )){
swapButtonEl.removeAttribute( 'Q-disabled' )
}
else swapButtonEl.setAttribute( 'Q-disabled', true )
}
-Q.Circuit.Editor.onCircuitChanged = function( circuitEl ){
+Editor.onCircuitChanged = function( circuitEl ){
const circuit = circuitEl.circuit
window.dispatchEvent( new CustomEvent(
@@ -6435,7 +6835,7 @@ Q.Circuit.Editor.onCircuitChanged = function( circuitEl ){
-Q.Circuit.Editor.unhighlightAll = function( circuitEl ){
+Editor.unhighlightAll = function( circuitEl ){
Array.from( circuitEl.querySelectorAll(
@@ -6460,7 +6860,7 @@ Q.Circuit.Editor.unhighlightAll = function( circuitEl ){
//////////////////////
-Q.Circuit.Editor.onPointerMove = function( event ){
+Editor.onPointerMove = function( event ){
// We need our cursor coordinates straight away.
@@ -6471,7 +6871,7 @@ Q.Circuit.Editor.onPointerMove = function( event ){
// and also see if one of those is a circuit board container.
const
- { x, y } = Q.Circuit.Editor.getInteractionCoordinates( event ),
+ { x, y } = Editor.getInteractionCoordinates( event ),
foundEls = document.elementsFromPoint( x, y ),
boardContainerEl = foundEls.find( function( el ){
@@ -6482,7 +6882,7 @@ Q.Circuit.Editor.onPointerMove = function( event ){
// Are we in the middle of a circuit clipboard drag?
// If so we need to move that thing!
- if( Q.Circuit.Editor.dragEl !== null ){
+ if( Editor.dragEl !== null ){
// ex. Don’t scroll on touch devices!
@@ -6494,11 +6894,11 @@ Q.Circuit.Editor.onPointerMove = function( event ){
// for a reality check on DOM coordinates:
// https://javascript.info/coordinates
- Q.Circuit.Editor.dragEl.style.left = ( x + window.pageXOffset + Q.Circuit.Editor.dragEl.offsetX ) +'px'
- Q.Circuit.Editor.dragEl.style.top = ( y + window.pageYOffset + Q.Circuit.Editor.dragEl.offsetY ) +'px'
+ Editor.dragEl.style.left = ( x + window.pageXOffset + Editor.dragEl.offsetX ) +'px'
+ Editor.dragEl.style.top = ( y + window.pageYOffset + Editor.dragEl.offsetY ) +'px'
- if( !boardContainerEl && Q.Circuit.Editor.dragEl.circuitEl ) Q.Circuit.Editor.dragEl.classList.add( 'Q-circuit-clipboard-danger' )
- else Q.Circuit.Editor.dragEl.classList.remove( 'Q-circuit-clipboard-danger' )
+ if( !boardContainerEl && Editor.dragEl.circuitEl ) Editor.dragEl.classList.add( 'Q-circuit-clipboard-danger' )
+ else Editor.dragEl.classList.remove( 'Q-circuit-clipboard-danger' )
}
@@ -6533,6 +6933,8 @@ Q.Circuit.Editor.onPointerMove = function( event ){
// Let’s prioritize any element that is “sticky”
// which means it can appear OVER another grid cell.
+
+
const
cellEl = foundEls.find( function( el ){
@@ -6596,10 +6998,10 @@ Q.Circuit.Editor.onPointerMove = function( event ){
boardElBounds = boardContainerEl.getBoundingClientRect(),
xLocal = x - boardElBounds.left + boardContainerEl.scrollLeft + 1,
yLocal = y - boardElBounds.top + boardContainerEl.scrollTop + 1,
- columnIndex = Q.Circuit.Editor.pointToGrid( xLocal ),
- rowIndex = Q.Circuit.Editor.pointToGrid( yLocal ),
- momentIndex = Q.Circuit.Editor.gridColumnToMomentIndex( columnIndex ),
- registerIndex = Q.Circuit.Editor.gridRowToRegisterIndex( rowIndex )
+ columnIndex = Editor.pointToGrid( xLocal ),
+ rowIndex = Editor.pointToGrid( yLocal ),
+ momentIndex = Editor.gridColumnToMomentIndex( columnIndex ),
+ registerIndex = Editor.gridRowToRegisterIndex( rowIndex )
// If this hover is “out of bounds”
@@ -6639,15 +7041,15 @@ Q.Circuit.Editor.onPointerMove = function( event ){
///////////////////////
-Q.Circuit.Editor.onPointerPress = function( event ){
+Editor.onPointerPress = function( event ){
// This is just a safety net
// in case something terrible has ocurred.
// (ex. Did the user click and then their mouse ran
// outside the window but browser didn’t catch it?)
- console.log("event target: ", event.target);
- if( Q.Circuit.Editor.dragEl !== null ){
- Q.Circuit.Editor.onPointerRelease( event )
+ if( Editor.dragEl !== null ){
+
+ Editor.onPointerRelease( event )
return
}
const
@@ -6670,7 +7072,7 @@ Q.Circuit.Editor.onPointerPress = function( event ){
dragEl = document.createElement( 'div' )
dragEl.classList.add( 'Q-circuit-clipboard' )
- const { x, y } = Q.Circuit.Editor.getInteractionCoordinates( event )
+ const { x, y } = Editor.getInteractionCoordinates( event )
// Are we dealing with a circuit interface?
@@ -6697,7 +7099,7 @@ Q.Circuit.Editor.onPointerPress = function( event ){
circuitEl.classList.add( 'Q-circuit-locked' )
lockEl.innerText = '🔒'
- Q.Circuit.Editor.unhighlightAll( circuitEl )
+ Editor.unhighlightAll( circuitEl )
}
@@ -6719,7 +7121,7 @@ Q.Circuit.Editor.onPointerPress = function( event ){
if( circuitIsLocked ) {
- Q.warn( `User attempted to interact with a circuit editor but it was locked.` )
+ logger.warn( `User attempted to interact with a circuit editor but it was locked.` )
return
}
@@ -6762,16 +7164,16 @@ Q.Circuit.Editor.onPointerPress = function( event ){
if( undoEl && circuit.history.undo$() ){
- Q.Circuit.Editor.onSelectionChanged( circuitEl )
- Q.Circuit.Editor.onCircuitChanged( circuitEl )
+ Editor.onSelectionChanged( circuitEl )
+ Editor.onCircuitChanged( circuitEl )
}
if( redoEl && circuit.history.redo$() ){
- Q.Circuit.Editor.onSelectionChanged( circuitEl )
- Q.Circuit.Editor.onCircuitChanged( circuitEl )
+ Editor.onSelectionChanged( circuitEl )
+ Editor.onCircuitChanged( circuitEl )
}
- if( controlEl ) Q.Circuit.Editor.createControl( circuitEl )
- if( swapEl ) Q.Circuit.Editor.createSwap( circuitEl )
+ if( controlEl ) Editor.createControl( circuitEl )
+ if( swapEl ) Editor.createSwap( circuitEl )
if( addMomentEl ) console.log( '→ Add moment' )
if( addRegisterEl ) console.log( '→ Add register' )
@@ -6795,8 +7197,8 @@ Q.Circuit.Editor.onPointerPress = function( event ){
const
momentIndex = +cellEl.getAttribute( 'moment-index' ),
registerIndex = +cellEl.getAttribute( 'register-index' ),
- columnIndex = Q.Circuit.Editor.momentIndexToGridColumn( momentIndex ),
- rowIndex = Q.Circuit.Editor.registerIndexToGridRow( registerIndex )
+ columnIndex = Editor.momentIndexToGridColumn( momentIndex ),
+ rowIndex = Editor.registerIndexToGridRow( registerIndex )
// Looks like our circuit is NOT locked
@@ -6853,7 +7255,7 @@ Q.Circuit.Editor.onPointerPress = function( event ){
el.classList.add( 'Q-circuit-cell-selected' )
})
}
- Q.Circuit.Editor.onSelectionChanged( circuitEl )
+ Editor.onSelectionChanged( circuitEl )
}
@@ -6890,9 +7292,9 @@ Q.Circuit.Editor.onPointerPress = function( event ){
// If we've doubleclicked on an operation and the operation has parameters, we should be able
// to edit those parameters regardless of whether or not the circuit is locked.
if( event.detail == 2) {
- const operation = Q.Gate.findBySymbol(operationEl.getAttribute( 'gate-symbol' ))
+ const operation = Gate.findBySymbol(operationEl.getAttribute( 'gate-symbol' ))
if( operation.has_parameters ) {
- Q.Circuit.Editor.onDoubleclick( event, operationEl )
+ Editor.onDoubleclick( event, operationEl )
return
}
}
@@ -6955,8 +7357,8 @@ Q.Circuit.Editor.onPointerPress = function( event ){
const
momentIndex = +el.getAttribute( 'moment-index' ),
registerIndex = +el.getAttribute( 'register-index' ),
- columnIndex = Q.Circuit.Editor.momentIndexToGridColumn( momentIndex ),
- rowIndex = Q.Circuit.Editor.registerIndexToGridRow( registerIndex )
+ columnIndex = Editor.momentIndexToGridColumn( momentIndex ),
+ rowIndex = Editor.registerIndexToGridRow( registerIndex )
columnIndexMin = Math.min( columnIndexMin, columnIndex )
rowIndexMin = Math.min( rowIndexMin, rowIndex )
@@ -6998,8 +7400,8 @@ Q.Circuit.Editor.onPointerPress = function( event ){
const
boardEl = circuitEl.querySelector( '.Q-circuit-board-foreground' ),
bounds = boardEl.getBoundingClientRect(),
- minX = Q.Circuit.Editor.gridToPoint( columnIndexMin ),
- minY = Q.Circuit.Editor.gridToPoint( rowIndexMin )
+ minX = Editor.gridToPoint( columnIndexMin ),
+ minY = Editor.gridToPoint( rowIndexMin )
dragEl.offsetX = bounds.left + minX - x
dragEl.offsetY = bounds.top + minY - y
@@ -7013,7 +7415,7 @@ Q.Circuit.Editor.onPointerPress = function( event ){
const
bounds = operationEl.getBoundingClientRect(),
- { x, y } = Q.Circuit.Editor.getInteractionCoordinates( event )
+ { x, y } = Editor.getInteractionCoordinates( event )
dragEl.appendChild( operationEl.cloneNode( true ))
dragEl.originEl = paletteEl
@@ -7028,11 +7430,11 @@ Q.Circuit.Editor.onPointerPress = function( event ){
operationEl = foregroundEl.querySelector( `[moment-index="${ parameterEl.getAttribute( 'operation-moment-index' )}"]` +
`[register-index="${ parameterEl.getAttribute( 'operation-register-index' )}"]` )
parameters = {}
- operationSkeleton = Q.Gate.findBySymbol( operationEl.getAttribute( 'gate-symbol' ))
+ operationSkeleton = Gate.findBySymbol( operationEl.getAttribute( 'gate-symbol' ))
Object.keys( operationSkeleton.parameters ).forEach( key => {
parameters[ key ] = operationEl.getAttribute( key ) ? operationEl.getAttribute( key ) : operationSkeleton.parameters[ key ]
})
- //on exiting the parameter-input-box, we should update the circuit!!
+ //upon exiting, we should update the circuit!!!
circuitEl.circuit.set$(
operationEl.getAttribute( 'gate-symbol' ),
+operationEl.getAttribute( 'moment-index' ),
@@ -7040,6 +7442,7 @@ Q.Circuit.Editor.onPointerPress = function( event ){
[ +operationEl.getAttribute( 'register-index' )],
parameters
)
+ //on exiting the parameter-input-box, we should update the circuit!!
parameterEl.innerHTML = ""
return
}
@@ -7051,8 +7454,8 @@ Q.Circuit.Editor.onPointerPress = function( event ){
// and trigger a draw of it in the correct spot.
document.body.appendChild( dragEl )
- Q.Circuit.Editor.dragEl = dragEl
- Q.Circuit.Editor.onPointerMove( event )
+ Editor.dragEl = dragEl
+ Editor.onPointerMove( event )
}
@@ -7067,14 +7470,13 @@ Q.Circuit.Editor.onPointerPress = function( event ){
/////////////////////////
-Q.Circuit.Editor.onPointerRelease = function( event ){
+Editor.onPointerRelease = function( event ){
// If there’s no dragEl then bail immediately.
- if( Q.Circuit.Editor.dragEl === null ) return
+ if( Editor.dragEl === null ) return
// Looks like we’re moving forward with this plan,
// so we’ll take control of the input now.
-
event.preventDefault()
event.stopPropagation()
@@ -7086,8 +7488,7 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// under the mouse / finger, skipping element [0]
// because that will be the clipboard.
- // doing this because elementsFromPoint() doesnt work well with JSDOM for testing purposes
- const { x, y } = Q.Circuit.Editor.getInteractionCoordinates( event )
+ const { x, y } = Editor.getInteractionCoordinates( event )
const boardContainerAll = document.querySelectorAll(".Q-circuit-board-container")
if( boardContainerAll.length === 0 ) return
let boardContainerEl = Array.from(boardContainerAll).find((element) => {
@@ -7106,20 +7507,20 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// If we were dragging from a palette
// we can just stop dragging.
- if( Q.Circuit.Editor.dragEl.circuitEl ){
+ if( Editor.dragEl.circuitEl ){
- Array.from( Q.Circuit.Editor.dragEl.children ).forEach( function( el ){
+ Array.from( Editor.dragEl.children ).forEach( function( el ){
- Q.Circuit.Editor.dragEl.originEl.appendChild( el )
+ Editor.dragEl.originEl.appendChild( el )
el.style.gridColumn = el.origin.columnIndex
el.style.gridRow = el.origin.rowIndex
if( el.wasSelected === true ) el.classList.remove( 'Q-circuit-cell-selected' )
else el.classList.add( 'Q-circuit-cell-selected' )
})
- Q.Circuit.Editor.onSelectionChanged( Q.Circuit.Editor.dragEl.circuitEl )
+ Editor.onSelectionChanged( Editor.dragEl.circuitEl )
}
- document.body.removeChild( Q.Circuit.Editor.dragEl )
- Q.Circuit.Editor.dragEl = null
+ document.body.removeChild( Editor.dragEl )
+ Editor.dragEl = null
}
@@ -7128,15 +7529,15 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
if( !boardContainerEl ){
- if( Q.Circuit.Editor.dragEl.circuitEl ){
+ if( Editor.dragEl.circuitEl ){
const
- originCircuitEl = Q.Circuit.Editor.dragEl.circuitEl
+ originCircuitEl = Editor.dragEl.circuitEl
originCircuit = originCircuitEl.circuit
originCircuit.history.createEntry$()
Array
- .from( Q.Circuit.Editor.dragEl.children )
+ .from( Editor.dragEl.children )
.forEach( function( child ){
originCircuit.clear$(
@@ -7145,8 +7546,8 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
child.origin.registerIndex
)
})
- Q.Circuit.Editor.onSelectionChanged( originCircuitEl )
- Q.Circuit.Editor.onCircuitChanged( originCircuitEl )
+ Editor.onSelectionChanged( originCircuitEl )
+ Editor.onCircuitChanged( originCircuitEl )
}
@@ -7154,12 +7555,12 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// Let’s keep a private reference to
// the current clipboard.
- let clipboardToDestroy = Q.Circuit.Editor.dragEl
+ let clipboardToDestroy = Editor.dragEl
// Now we can remove our dragging reference.
- Q.Circuit.Editor.dragEl = null
+ Editor.dragEl = null
// Add our CSS animation routine
@@ -7211,13 +7612,13 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
bounds = boardContainerEl.getBoundingClientRect(),
droppedAtX = x - bounds.left + boardContainerEl.scrollLeft,
droppedAtY = y - bounds.top + boardContainerEl.scrollTop,
- droppedAtMomentIndex = Q.Circuit.Editor.gridColumnToMomentIndex(
+ droppedAtMomentIndex = Editor.gridColumnToMomentIndex(
- Q.Circuit.Editor.pointToGrid( droppedAtX )
+ Editor.pointToGrid( droppedAtX )
),
- droppedAtRegisterIndex = Q.Circuit.Editor.gridRowToRegisterIndex(
+ droppedAtRegisterIndex = Editor.gridRowToRegisterIndex(
- Q.Circuit.Editor.pointToGrid( droppedAtY )
+ Editor.pointToGrid( droppedAtY )
),
foregroundEl = circuitEl.querySelector( '.Q-circuit-board-foreground' )
@@ -7225,9 +7626,9 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// If this is a self-drop
// we can also just return to origin and bail.
- if( Q.Circuit.Editor.dragEl.circuitEl === circuitEl &&
- Q.Circuit.Editor.dragEl.momentIndex === droppedAtMomentIndex &&
- Q.Circuit.Editor.dragEl.registerIndex === droppedAtRegisterIndex ){
+ if( Editor.dragEl.circuitEl === circuitEl &&
+ Editor.dragEl.momentIndex === droppedAtMomentIndex &&
+ Editor.dragEl.registerIndex === droppedAtRegisterIndex ){
returnToOrigin()
return
@@ -7255,9 +7656,9 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// where they need to go!
const
- draggedOperations = Array.from( Q.Circuit.Editor.dragEl.children ),
- draggedMomentDelta = droppedAtMomentIndex - Q.Circuit.Editor.dragEl.momentIndex,
- draggedRegisterDelta = droppedAtRegisterIndex - Q.Circuit.Editor.dragEl.registerIndex,
+ draggedOperations = Array.from( Editor.dragEl.children ),
+ draggedMomentDelta = droppedAtMomentIndex - Editor.dragEl.momentIndex,
+ draggedRegisterDelta = droppedAtRegisterIndex - Editor.dragEl.registerIndex,
setCommands = []
@@ -7278,10 +7679,10 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
momentIndexTarget = droppedAtMomentIndex,
registerIndexTarget = droppedAtRegisterIndex
- if( Q.Circuit.Editor.dragEl.circuitEl ){
+ if( Editor.dragEl.circuitEl ){
- momentIndexTarget += childEl.origin.momentIndex - Q.Circuit.Editor.dragEl.momentIndex
- registerIndexTarget += childEl.origin.registerIndex - Q.Circuit.Editor.dragEl.registerIndex
+ momentIndexTarget += childEl.origin.momentIndex - Editor.dragEl.momentIndex
+ registerIndexTarget += childEl.origin.registerIndex - Editor.dragEl.registerIndex
}
@@ -7311,7 +7712,7 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
foundComponents = Array.from(
- Q.Circuit.Editor.dragEl.querySelectorAll(
+ Editor.dragEl.querySelectorAll(
`[moment-index="${ childEl.origin.momentIndex }"]`+
`[register-indices="${ registerIndicesString }"]`
@@ -7325,7 +7726,7 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
return aRegisterIndicesIndex - bRegisterIndicesIndex
}),
- allComponents = Array.from( Q.Circuit.Editor.dragEl.circuitEl.querySelectorAll(
+ allComponents = Array.from( Editor.dragEl.circuitEl.querySelectorAll(
`[moment-index="${ childEl.origin.momentIndex }"]`+
`[register-indices="${ registerIndicesString }"]`
@@ -7341,7 +7742,7 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// because that will be an identity / control / null gate.
// We need to look at slot 1.
- component1 = Q.Circuit.Editor.dragEl.querySelector(
+ component1 = Editor.dragEl.querySelector(
`[moment-index="${ childEl.origin.momentIndex }"]`+
`[register-index="${ registerIndices[ 1 ] }"]`
@@ -7360,7 +7761,7 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
draggedOperations.forEach( function( childEl ){
- Q.Circuit.Editor.dragEl.circuitEl.circuit.clear$(
+ Editor.dragEl.circuitEl.circuit.clear$(
childEl.origin.momentIndex,
childEl.origin.registerIndex
@@ -7375,7 +7776,7 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
if( registerIndices.length === foundComponents.length ){
- const operationSkeleton = Q.Gate.findBySymbol( gatesymbol )
+ const operationSkeleton = Gate.findBySymbol( gatesymbol )
parameters = {}
if( operationSkeleton.has_parameters ) {
Object.keys( operationSkeleton.parameters ).forEach( key => {
@@ -7397,9 +7798,9 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
const siblingDelta = registerIndex - childEl.origin.registerIndex
registerIndexTarget = droppedAtRegisterIndex
- if( Q.Circuit.Editor.dragEl.circuitEl ){
+ if( Editor.dragEl.circuitEl ){
- registerIndexTarget += childEl.origin.registerIndex - Q.Circuit.Editor.dragEl.registerIndex + siblingDelta
+ registerIndexTarget += childEl.origin.registerIndex - Editor.dragEl.registerIndex + siblingDelta
}
return registerIndexTarget
}),
@@ -7416,7 +7817,7 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// and we’re staying within the same moment index
// that might be ok!
- else if( Q.Circuit.Editor.dragEl.circuitEl === circuitEl &&
+ else if( Editor.dragEl.circuitEl === circuitEl &&
momentIndexTarget === childEl.origin.momentIndex ){
@@ -7455,7 +7856,7 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
const
componentRegisterIndex = +component.getAttribute( 'register-index' ),
- registerGrabDelta = componentRegisterIndex - Q.Circuit.Editor.dragEl.registerIndex
+ registerGrabDelta = componentRegisterIndex - Editor.dragEl.registerIndex
// Now put it where it wants to go,
@@ -7485,10 +7886,10 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// ie. if a dragged sibling overwrote a seated one.
.filter( function( entry ){
- return Q.isUsefulInteger( entry )
+ return mathf.isUsefulInteger( entry )
})
- const operationSkeleton = Q.Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ) )
+ const operationSkeleton = Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ) )
parameters = {}
if( operationSkeleton.has_parameters ) {
Object.keys( operationSkeleton.parameters ).forEach( key => {
@@ -7499,8 +7900,8 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// circuit.set$(
setCommands.push([
//ltnln: if a component of an operation that requires a sibling pair overwrites its sibling, we want it removed entirely.
- fixedRegistersIndices.length < 2 && Q.Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ) ).is_multi_qubit ?
- Q.Gate.NOOP :
+ fixedRegistersIndices.length < 2 && Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ) ).is_multi_qubit ?
+ Gate.NOOP :
childEl.getAttribute( 'gate-symbol' ),
momentIndexTarget,
fixedRegistersIndices,
@@ -7511,7 +7912,7 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
else {
remainingComponents.forEach( function( componentEl, i ){
//circuit.set$(
- const operationSkeleton = Q.Gate.findBySymbol( componentEl.getAttribute( 'gate-symbol' ) )
+ const operationSkeleton = Gate.findBySymbol( componentEl.getAttribute( 'gate-symbol' ) )
parameters = {}
if( operationSkeleton.has_parameters ) {
Object.keys( operationSkeleton.parameters ).forEach( key => {
@@ -7520,9 +7921,9 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
}
setCommands.push([
- +componentEl.getAttribute( 'register-indices-index' ) && !Q.Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ) ).is_multi_qubit ?
+ +componentEl.getAttribute( 'register-indices-index' ) && !Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ) ).is_multi_qubit ?
gatesymbol :
- Q.Gate.NOOP,
+ Gate.NOOP,
+componentEl.getAttribute( 'moment-index' ),
+componentEl.getAttribute( 'register-index' ),
parameters
@@ -7535,7 +7936,7 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// all the components that were part of the drag.
foundComponents.forEach( function( componentEl ){
- const operationSkeleton = Q.Gate.findBySymbol( componentEl.getAttribute( 'gate-symbol' ) )
+ const operationSkeleton = Gate.findBySymbol( componentEl.getAttribute( 'gate-symbol' ) )
parameters = {}
if( operationSkeleton.has_parameters ) {
Object.keys( operationSkeleton.parameters ).forEach( key => {
@@ -7545,9 +7946,9 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
setCommands.push([
//ltnln: temporary fix: certain multiqubit operations should only be represented in pairs of registers. If one is removed (i.e. a single component of the pair)
//then the entire operation should be removed.
- +componentEl.getAttribute( 'register-indices-index' ) && !Q.Gate.findBySymbol( componentEl.getAttribute( 'gate-symbol' ) ).is_multi_qubit ?
+ +componentEl.getAttribute( 'register-indices-index' ) && !Gate.findBySymbol( componentEl.getAttribute( 'gate-symbol' ) ).is_multi_qubit ?
componentEl.getAttribute( 'gate-symbol' ) :
- Q.Gate.NOOP,
+ Gate.NOOP,
+componentEl.getAttribute( 'moment-index' ) + draggedMomentDelta,
+componentEl.getAttribute( 'register-index' ) + draggedRegisterDelta,
parameters
@@ -7589,11 +7990,11 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// (and not a circuit palette)
// make sure the old positions are cleared away.
- if( Q.Circuit.Editor.dragEl.circuitEl ){
+ if( Editor.dragEl.circuitEl ){
draggedOperations.forEach( function( childEl ){
- Q.Circuit.Editor.dragEl.circuitEl.circuit.clear$(
+ Editor.dragEl.circuitEl.circuit.clear$(
childEl.origin.momentIndex,
childEl.origin.registerIndex
@@ -7609,7 +8010,7 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
let registerIndices = [ registerIndexTarget ]
//ltnln: By default, multiqubit gates appear in pairs on the circuit rather than
// requiring the user to have to pair them like with Swap/CNot.
- const operationSkeleton = Q.Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ))
+ const operationSkeleton = Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ))
if(operationSkeleton.is_multi_qubit ) {
registerIndices.push( registerIndexTarget == circuit.bandwidth ? registerIndexTarget - 1 : registerIndexTarget + 1)
}
@@ -7640,20 +8041,20 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// Are we capable of making controls? Swaps?
- Q.Circuit.Editor.onSelectionChanged( circuitEl )
- Q.Circuit.Editor.onCircuitChanged( circuitEl )
+ Editor.onSelectionChanged( circuitEl )
+ Editor.onCircuitChanged( circuitEl )
// If the original circuit and destination circuit
// are not the same thing
// then we need to also eval the original circuit.
- if( Q.Circuit.Editor.dragEl.circuitEl &&
- Q.Circuit.Editor.dragEl.circuitEl !== circuitEl ){
+ if( Editor.dragEl.circuitEl &&
+ Editor.dragEl.circuitEl !== circuitEl ){
- const originCircuitEl = Q.Circuit.Editor.dragEl.circuitEl
- Q.Circuit.Editor.onSelectionChanged( originCircuitEl )
- Q.Circuit.Editor.onCircuitChanged( originCircuitEl )
+ const originCircuitEl = Editor.dragEl.circuitEl
+ Editor.onSelectionChanged( originCircuitEl )
+ Editor.onCircuitChanged( originCircuitEl )
}
@@ -7662,8 +8063,8 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// It’s been a long journey.
// I love you all.
- document.body.removeChild( Q.Circuit.Editor.dragEl )
- Q.Circuit.Editor.dragEl = null
+ document.body.removeChild( Editor.dragEl )
+ Editor.dragEl = null
}
@@ -7673,12 +8074,9 @@ Q.Circuit.Editor.onPointerRelease = function( event ){
// //
/////////////////////////
//ltnln: my trying out an idea for parameterized gates...
-Q.Circuit.Editor.onDoubleclick = function( event, operationEl ) {
- // assumption for the following 3 lines is that we've already decided that we are on-top of a valid gate operation in
- // the circuit
- const operation = Q.Gate.findBySymbol( operationEl.getAttribute( 'gate-symbol' ))
- const { x, y } = Q.Circuit.Editor.getInteractionCoordinates( event )
-
+Editor.onDoubleclick = function( event, operationEl ) {
+ const operation = Gate.findBySymbol( operationEl.getAttribute( 'gate-symbol' ))
+ const { x, y } = Editor.getInteractionCoordinates( event )
const boardContainerAll = document.querySelectorAll(".Q-circuit-board-container")
if( boardContainerAll.length === 0 ) return
let boardContainerEl = Array.from(boardContainerAll).find((element) => {
@@ -7691,14 +8089,10 @@ Q.Circuit.Editor.onDoubleclick = function( event, operationEl ) {
})
if( !boardContainerEl ) return;
const parameterEl = boardContainerEl.querySelector('.Q-parameters-box')
- const exit = document.createElement( 'button' )
- parameterEl.appendChild( exit )
- exit.classList.add( 'Q-parameter-box-exit' )
+ const exit = Editor.createNewElement( 'button', parameterEl, 'Q-parameter-box-exit')
exit.appendChild(document.createTextNode( '⬅' ))
parameterEl.setAttribute( "operation-moment-index", operationEl.getAttribute( 'moment-index' ))
parameterEl.setAttribute( "operation-register-index", operationEl.getAttribute( 'register-index' ))
-
-
//here we generate queries for each parameter that the gate operation takes!
const parameters = Object.keys(operation.parameters)
parameters.forEach( element => {
@@ -7706,46 +8100,35 @@ Q.Circuit.Editor.onDoubleclick = function( event, operationEl ) {
const input_fields = document.createElement( 'div' )
parameterEl.appendChild( input_fields )
input_fields.classList.add( 'Q-parameter-box-input-container' )
- const label = document.createElement( "span" )
- input_fields.appendChild( label )
- label.classList.add( 'Q-parameter-input-label' )
+
+ const label = Editor.createNewElement( "span", input_fields, 'Q-parameter-input-label' )
label.appendChild(document.createTextNode( element ))
- const textbox = document.createElement( "input" )
- input_fields.appendChild( textbox )
- textbox.classList.add( 'Q-parameter-box-input' )
+ const textbox = Editor.createNewElement( "input", input_fields, 'Q-parameter-box-input')
textbox.setAttribute( 'type', 'text' )
textbox.setAttribute( 'placeholder', element )
textbox.setAttribute( 'value', operationEl.getAttribute(element) ? operationEl.getAttribute(element) : operation.parameters[element] )
//set textbox to update the operation instance (cellEl)'s parameters on value change
textbox.addEventListener( "change", () => {
- let parameterValue
+ let parameterValue = +textbox.value;
let oldValue = operationEl.getAttribute( element )
if( !oldValue ) oldValue = operation.parameters[ element ]
- try {
- //TODO: figure out how to properly import the mathjs library...
- parameterValue = +(textbox.value.toLowerCase());
- }
- catch( err ) {
- parameterValue = oldValue
- }
-
- if( !parameterValue || parameterValue === Infinity ) textbox.value = oldValue.toString()
+ if( parameterValue === null || parameterValue === Infinity ) textbox.value = oldValue.toString()
else {
operationEl.setAttribute( element, parameterValue )
- textbox.value = parameterValue.toString()
+ textbox.value = parameterValue
}
})
}
})
-
parameterEl.classList.toggle('overlay')
parameterEl.style.display = 'block'
}
+
///////////////////
// //
// Listeners //
@@ -7753,44 +8136,21 @@ Q.Circuit.Editor.onDoubleclick = function( event, operationEl ) {
///////////////////
-// These listeners must be applied
+// These listeners must be appliedm
// to the entire WINDOW (and not just document.body!)
-window.addEventListener( 'mousemove', Q.Circuit.Editor.onPointerMove )
-window.addEventListener( 'touchmove', Q.Circuit.Editor.onPointerMove )
-window.addEventListener( 'mouseup', Q.Circuit.Editor.onPointerRelease )
-window.addEventListener( 'touchend', Q.Circuit.Editor.onPointerRelease )
-
-
-
-
-
-
-
-/*
-
-
-%%HTML
-
-
-
+window.addEventListener( 'mousemove', Editor.onPointerMove )
+window.addEventListener( 'touchmove', Editor.onPointerMove )
+window.addEventListener( 'mouseup', Editor.onPointerRelease )
+window.addEventListener( 'touchend', Editor.onPointerRelease )
+module.exports = {Editor}
+},{"quantum-js-util":13}],16:[function(require,module,exports){
+const {Editor} = require('./Q-Circuit-Editor');
+const {circuit} = require('quantum-js-util');
+const {BlochSphere} = require('./Q-BlochSphere');
+console.log("Welcome to Q.js! The GUI experience!\n");
-
-%%javascript
-Q.braket( element )
-
-
-
-
-*/
-
-
-
-//%%javascript
-
-
-
-Q.braket = function(){
+braket = function(){
// Create the HTML bits we need,
@@ -7810,10 +8170,12 @@ Q.braket = function(){
circuit = new Q( args[0], args[1] )
}
container = document.createElement( 'div' )
- container.appendChild( Q.Circuit.Editor.createPalette() )
+ let paletteEl = Editor.createPalette();
+ paletteEl.style.width = "50%";
+ container.appendChild( paletteEl );
container.appendChild( circuit.toDom() )
element.html( container )
-
+
// We’re going to take this SLOOOOOOOOWLY
// because there are many potential things to debug.
@@ -7848,13 +8210,14 @@ Q.braket = function(){
}
})
- window.addEventListener( 'Q.Circuit.evaluate completed', function( event ) {
+ window.addEventListener( 'Circuit.evaluate completed', function( event ) {
if( event.detail.circuit === circuit ) {
nextNextCell.set_text( circuit.report$() )
}
})
+
// nextCell.render()
// console.log( 'thisCell', thisCell )
@@ -7871,4 +8234,6 @@ Q.braket = function(){
// Jupyter.notebook.get_cell(t_index).render()
}
-module.exports = Q
\ No newline at end of file
+
+module.exports = {Editor, BlochSphere, braket};
+},{"./Q-BlochSphere":14,"./Q-Circuit-Editor":15,"quantum-js-util":13}]},{},[1]);
diff --git a/build/q.css b/build/q.css
index de56074..e4a6620 100644
--- a/build/q.css
+++ b/build/q.css
@@ -1,6 +1,6 @@
/*
- Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+ Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
*/
@charset "utf-8";
@@ -11,8 +11,8 @@
/*
This file is in the process of being separated
- in to “essential global Q.js styles” which will
- remain here in Q.css, and “documentation-specific”
+ in to “essential global Q.js styles†which will
+ remain here in Q.css, and “documentation-specificâ€
styles which will be removed from here and placed
within the /other/documentation.css file instead.
@@ -319,7 +319,7 @@ svg, :root {
/*
- The below still need to be prefaced with “Q-”
+ The below still need to be prefaced with “Q-â€
and for the HTML pages to be updated accordingly.
*/
@@ -341,6 +341,8 @@ svg, :root {
max-width: 100%;
overflow-x: auto;
font-family: var( --Q-font-family-sans );
+ /*letter-spacing: 0.03em;*/
+ word-spacing: 0.2em;
}
dd .maths {
@@ -393,22 +395,23 @@ dd .maths {
vertical-align: middle;
position: relative;
align: middle;
- margin: 1em;
+ margin: 1em 0.5em;
padding: 1em;
- font-family: var( --Q-font-family-mono );
+ /*font-family: var( --Q-font-family-mono );*/
font-weight: 300;
line-height: 1em;
- text-align: right;
+ /*text-align: right;*/
+ text-align: center;
}
.matrix td {
- padding: 5px 10px;
+ padding: 0.25em 0.5em;
}
.matrix-bracket-left, .matrix-bracket-right {
position: absolute;
top: 0;
- width: 5px;
+ width: 0.5em;
height: 100%;
border: 1px solid #CCC;
}
@@ -437,7 +440,7 @@ dd .maths {
.Q-state-vector.bra::before,
.complex-vector.bra::before {
- content: '⟨';
+ content: '⟨';
color: #BBB;
}
.Q-state-vector.bra::after,
@@ -455,7 +458,7 @@ dd .maths {
.Q-state-vector.ket::after,
.complex-vector.ket::after {
- content: '⟩';
+ content: '⟩';
color: #BBB;
}
.Q-state-vector.bra + .Q-state-vector.ket::before,
@@ -472,7 +475,7 @@ dd .maths {
/*
- Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+ Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
*/
@charset "utf-8";
@@ -484,7 +487,6 @@ dd .maths {
-
/*
Z indices:
@@ -567,11 +569,12 @@ dd .maths {
+
.Q-circuit,
.Q-circuit-palette {
position: relative;
- width: 100%;
+ width: 50%;
}
.Q-circuit-palette {
@@ -595,6 +598,7 @@ dd .maths {
margin: 1rem 0 2rem 0;
/*border-top: 2px solid hsl( 0, 0%, 50% );*/
}
+.Q-parameters-box,
.Q-circuit-board-foreground {
line-height: 3.85rem;
@@ -747,6 +751,19 @@ dd .maths {
grid-auto-columns: 4rem;
grid-auto-flow: column;
}
+
+.Q-parameters-box {
+
+ position: absolute;
+ display: none;
+ z-index: 100;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: whitesmoke;
+}
+
/*.Q-circuit-palette,*/
.Q-circuit-board-foreground,
.Q-circuit-board-background {
@@ -836,8 +853,17 @@ dd .maths {
);
}
+.Q-parameter-box-exit {
+ position: relative;
+ right: 0;
+ left: 0;
+ width: 5rem;
+ height: 2.5rem;
+ background-color: whitesmoke;
+ border-radius: 0;
+}
-
+.Q-parameters-box > div,
.Q-circuit-palette > div,
.Q-circuit-clipboard > div,
.Q-circuit-board-foreground > div {
@@ -1079,7 +1105,7 @@ dd .maths {
rgba( 0, 0, 0, 0.05 )
)*/;
}
-.Q-circuit-palette .Q-circuit-operation:hover {
+.Q-parameter-box-exit .Q-circuit-palette .Q-circuit-operation:hover {
/*background-color: rgba( 255, 255, 255, 0.6 );*/
background-color: white;
@@ -1192,6 +1218,25 @@ dd .maths {
}
+.Q-parameter-box-input-container {
+ position: relative;
+ text-align: center;
+ grid-auto-columns: 4rem;
+ grid-auto-flow: column;
+}
+
+.Q-parameter-box-input {
+ position: relative;
+ border-radius: .2rem;
+ margin-left: 10px;
+ font-family: var( --Q-font-family-mono );
+}
+
+.Q-parameter-input-label {
+ position: relative;
+ color: var( --Q-color-blue );
+ font-family: var( --Q-font-family-mono );
+}
@@ -1329,4 +1374,3 @@ dd .maths {
}
-
diff --git a/contributing.html b/contributing.html
index c8a3316..a516c2c 100644
--- a/contributing.html
+++ b/contributing.html
@@ -40,20 +40,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
+
diff --git a/index.html b/index.html
index 1aa2898..c513938 100644
--- a/index.html
+++ b/index.html
@@ -40,20 +40,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
+
@@ -585,7 +578,7 @@ Import and export
// console.log( 'state width', state.getWidth(), 'state height', state.getHeight() )
// console.log( 'state', state.toTsv() )
})
-window.addEventListener( 'Q.Circuit.evaluate completed', function( event ){
+window.addEventListener( 'Circuit.evaluate completed', function( event ){
const circuit = event.detail.circuit
console.log(
@@ -624,7 +617,7 @@ Import and export
// Demonstrate Q’s random naming feature.
-const circuitNameRandom = Q.getRandomName$()
+const circuitNameRandom = misc.getRandomName$()
Array
.from( document.querySelectorAll( '.circuit-name-random' ))
@@ -639,8 +632,8 @@ Import and export
// so wee can begin playing with them straight away.
var
-cat = new Q.ComplexNumber( 1, 2 ),
-dog = new Q.ComplexNumber( 3, -4 )
+cat = new ComplexNumber( 1, 2 ),
+dog = new ComplexNumber( 3, -4 )
@@ -661,7 +654,7 @@ Import and export
.from( document.querySelectorAll( '.Q-circuit-palette' ))
.forEach( function( el ){
- Q.Circuit.Editor.createPalette( el )
+ Editor.createPalette( el )
})
diff --git a/index.js b/index.js
index fc40a83..840985e 100644
--- a/index.js
+++ b/index.js
@@ -1,13 +1,6 @@
-const {Q} = require('./Q/Q');
-const {Circuit} = require('./Q/Q-Circuit');
-const {Qubit} = require('./Q/Q-Qubit');
-const {Gate} = require('./Q/Q-Gate');
-const {Matrix} = require('./Q/Q-Matrix');
-const {ComplexNumber} = require('./Q/Q-ComplexNumber');
-const mathf = require('./Q/Math-Functions');
-const misc = require('./Q/Misc');
-const logger = require('./Q/Logging');
+let {logger, mathf, misc, ComplexNumber, Matrix, Gate, Qubit, Circuit, History, Q} = require('quantum-js-util');
+let {Editor, BlochSphere, braket} = require('quantum-js-vis');
+global.misc = misc;
+global.logger = logger;
+global.mathf = mathf;
-console.log("Howdy! Welcome to Q.js!");
-
-module.exports = {Q, Circuit, Qubit, Gate, Matrix, ComplexNumber, mathf, misc, logger};
\ No newline at end of file
diff --git a/package.json b/package.json
index 0573d1b..82cdd1c 100644
--- a/package.json
+++ b/package.json
@@ -4,9 +4,11 @@
"description": "\")",
"main": "Q/Q.js",
"scripts": {
- "test": "npm run test -ws && exit 0",
- "lint": "eslint",
- "prettier": "prettier --write"
+ "test": "npm run test -ws",
+ "lint": "npm run lint -ws",
+ "prettier": "npm run prettier -ws",
+ "dev": "vite",
+ "build": "npx browserify index.js > build/bundle.js && npx concat -o build/bundle.css packages/quantum-js-vis/Q.css packages/quantum-js-vis/Q-Circuit-Editor.css"
},
"repository": {
"type": "git",
@@ -16,6 +18,8 @@
"author": "",
"license": "ISC",
"devDependencies": {
+ "browserify": "^17.0.0",
+ "concat": "^1.0.3",
"eslint": "^7.31.0",
"jest": "^27.0.6",
"jsdom": "^16.6.0",
@@ -23,7 +27,8 @@
"n": "^7.3.1",
"prettier": "2.3.2"
},
- "dependencies": {},
+ "dependencies": {
+ },
"workspaces": [
"./packages/*"
]
diff --git a/packages/quantum-js-cli/__test__/runner.test.js b/packages/quantum-js-cli/__test__/runner.test.js
new file mode 100644
index 0000000..c74c0e2
--- /dev/null
+++ b/packages/quantum-js-cli/__test__/runner.test.js
@@ -0,0 +1,258 @@
+const runner = require('../runner');
+const quantumjs = require('quantum-js-util');
+
+
+describe("Checking evaluateInput calls the correct functions and/or logs the correct information given a certain input", () => {
+ //create empty circuit.
+ let circuit = quantumjs.Q();
+ console.log = jest.fn();
+ test("Testing evaluateInput() with input 'help' and an empty circuit.", () => {
+ let expectedText = runner.printMenu();
+ runner.evaluateInput("help", circuit);
+ expect(console.log).toHaveBeenCalled();
+ expect(console.log).toHaveBeenCalledWith(expectedText);
+ })
+ test("Testing evaluateInput() with input '-h' and an empty circuit.", () => {
+ let expectedText = runner.printMenu();
+ runner.evaluateInput("-h", circuit);
+ expect(console.log).toHaveBeenCalled();
+ expect(console.log).toHaveBeenCalledWith(expectedText);
+ })
+ test("Testing evaluateInput() with input 'toAmazonBraket' and an empty circuit", () => {
+ runner.evaluateInput("toAmazonBraket", circuit);
+ expectedText = circuit.toAmazonBraket();
+ expect(console.log).toHaveBeenCalled();
+ expect(console.log).toHaveBeenCalledWith(expectedText);
+ })
+ test("Testing evaluateInput() with input 'toDiagram' and an empty circuit", () => {
+ runner.evaluateInput("toDiagram", circuit);
+ expectedText = circuit.toDiagram();
+ expect(console.log).toHaveBeenCalled();
+ expect(console.log).toHaveBeenCalledWith(expectedText);
+ })
+ test("Testing evaluateInput() with input 'toLaTeX' and an empty circuit", () => {
+ runner.evaluateInput("toLaTeX", circuit);
+ expectedText = circuit.toLatex();
+ expect(console.log).toHaveBeenCalled();
+ expect(console.log).toHaveBeenCalledWith(expectedText);
+ })
+ test("Testing evaluateInput() with input 'report' and an empty circuit", () => {
+ runner.evaluateInput("report", circuit);
+ expectedText = circuit.report$();
+ expect(console.log).toHaveBeenCalled();
+ expect(console.log).toHaveBeenCalledWith(expectedText);
+ })
+ test("Testing evaluateInput() with input 'toText' and an empty circuit", () => {
+ runner.evaluateInput("toText", circuit);
+ expectedText = circuit.toAmazonBraket();
+ expect(console.log).toHaveBeenCalled();
+ expect(console.log).toHaveBeenCalledWith(expectedText);
+ })
+ test("Testing evaluateInput() with input 'clear' and an empty circuit", () => {
+ console.clear = jest.fn();
+ runner.evaluateInput("clear", circuit);
+ expectedText = circuit.toAmazonBraket();
+ expect(console.clear).toHaveBeenCalled();
+ })
+})
+
+//Gate operation syntax is of the regex form /(\w+\(\s*\d+\s*,\s*\[(\s*\d+\s*,{0,1}\s*)+\](,\s*\d+\.{0,1}\d+\s*)*\))/g
+//or more easily described:
+//'gate-symbol(moment-index, [registerIndex0, registerIndex1,...], parameter0, parameter1,...)' with white space allowed liberally
+describe("Testing various forms of gate-operation call expression and see that evaluateOperation is called", ()=> {
+ //create empty circuit.
+ test("Check that evaluateOperation is called once for input 'h(1, [1])'", () => {
+ let circuit = quantumjs.Q();
+ runner.evaluateInput("h(1, [1])", circuit);
+ expect(circuit.get(1, 1).gate.symbol).toBe('H');
+ })
+ //messing with the white space
+ test("Check that evaluateOperation is called once for input 'h( 1 , [ 1 ])'", () => {
+ let circuit = quantumjs.Q();
+ runner.evaluateInput("h( 1 , [ 1 ])", circuit)
+ expect(circuit.get(1, 1).gate.symbol).toBe('H');
+ })
+ //while this doesn't update the circuit, it should still call evaluateOperation which detects the flaw later.
+ test("Check that no operation is added for 'h( 1 , [ 1 ], 3)' as the Hadamard operation takes no parameters", () => {
+ let circuit = quantumjs.Q();
+ runner.evaluateInput("h( 1 , [ 1 ], 3)", circuit)
+ expect(circuit.get(1, 1)).toBeUndefined();
+ })
+ test("Check that evaluateOperation is called for input 'x(1, [1, 2])'", () => {
+ let circuit = quantumjs.Q();
+ runner.evaluateInput("x(1, [1, 2])", circuit)
+ expect(circuit.get(1, 1).gate.symbol).toBe("X");
+ expect(circuit.get(1, 2).gate.symbol).toBe("X");
+ })
+ //messing with the white space
+ test("Check that evaluateOperation is called for input 'x( 1, [ 1, 2 ] )'", () => {
+ let circuit = quantumjs.Q();
+ runner.evaluateInput("x(1, [1, 2])", circuit)
+ expect(circuit.get(1, 1).gate.symbol).toBe("X");
+ expect(circuit.get(1, 2).gate.symbol).toBe("X");
+ })
+ describe("Check that gateSymbol(...,[...]) is valid for all gate constants in the Gate module using their nameCss value", ()=> {
+ Object.entries(quantumjs.Gate.constants).forEach(function(entry) {
+ let gate = entry[1];
+ let input = gate.nameCss + (gate.is_multi_qubit ? "(1, [1, 2])" : "(1, [1])");
+ console.log(input);
+ test("Checking that evaluate operation is called once for the input '" + input + "'", () => {
+ let circuit = quantumjs.Q();
+ runner.evaluateInput(input, circuit);
+ expect(circuit.get(1, 1).gate.nameCss).toBe(gate.nameCss);
+ if(gate.is_multi_qubit) {
+ expect(circuit.get(1, 2).gate.nameCss).toBe(gate.nameCss);
+ }
+ })
+ })
+ })
+})
+
+
+describe("Testing removal operations (of the same regex form as above) and that removeOperation() is called", () => {
+ test("Check that removeOperation is called once for input remove(1, [1])", () => {
+ let circuit = quantumjs.Q();
+ //Set a hadamard operation on the circuit.
+ circuit.set$('H', 1, [1]);
+ runner.evaluateInput('remove(1, [1])', circuit);
+ expect(circuit.get(1, 1)).toBeUndefined();
+ })
+ //messing with the whitepsace
+ test("Check that removeOperation is called once for input remove( 1 , [ 1 ] )", () => {
+ let circuit = quantumjs.Q();
+ //Set a hadamard operation on the circuit.
+ circuit.set$('H', 1, [1]);
+ runner.evaluateInput('remove( 1 , [ 1 ] )', circuit);
+ expect(circuit.get(1, 1)).toBeUndefined();
+ })
+ test("Check that removeOperation removes all sibling indices of the operation x(1, [1, 2]) when remove(1, [1]) is called", ()=> {
+ let circuit = quantumjs.Q();
+ circuit.set$('X', 1, [1, 2]);
+ runner.evaluateInput('remove(1, [1])', circuit);
+ expect(circuit.get(1, 1)).toBeUndefined();
+ expect(circuit.get(1, 2)).toBeUndefined();
+ })
+ test("Check that the removeOperation does NOT remove any operation given a set of indices that are not siblings", ()=> {
+ let circuit = quantumjs.Q();
+ //Set a hadamard operation on the circuit.
+ circuit.set$('H', 1, [1]);
+ runner.evaluateInput('remove(1, [1, 2])', circuit);
+ expect(circuit.get(1, 1)).toBeDefined();
+ expect(circuit.get(1, 1).gate.symbol).toBe('H');
+ })
+})
+
+describe("Check that parameters parameterized gates can be input and set properly", ()=> {
+ test("Check that the input 'u(1, [1], 3, 2, 5)' results in the creation of a unitary gate with phi=3,theta=2,lambda=5", ()=>{
+ let circuit = quantumjs.Q(4, 4);
+ runner.evaluateInput("u(1, [1], 3, 2, 5)", circuit);
+ let result = circuit.get(1, 1).gate;
+ expect(result.symbol).toBe('U');
+ expect(result.parameters['phi']).toBe(3);
+ expect(result.parameters['theta']).toBe(2);
+ expect(result.parameters['lambda']).toBe(5);
+ })
+ //messing with whitespace
+ test("Check that the input 'u(1, [1], 3 , 2 , 5 )' results in the creation of a unitary gate with phi=3,theta=2,lambda=5", ()=>{
+ let circuit = quantumjs.Q(4, 4);
+ runner.evaluateInput("u(1, [1], 3 , 2 , 5 )", circuit);
+ let result = circuit.get(1, 1).gate;
+ expect(result.symbol).toBe('U');
+ expect(result.parameters['phi']).toBe(3);
+ expect(result.parameters['theta']).toBe(2);
+ expect(result.parameters['lambda']).toBe(5);
+ })
+ //checking regex accepts decimal values
+ test("Check that the input 'u(1, [1], 3.93, 2.24, 5.12)' results in the creation of a unitary gate with phi=3,theta=2,lambda=5", ()=>{
+ let circuit = quantumjs.Q(4, 4);
+ runner.evaluateInput("u(1, [1], 3.93, 2.24, 5.12)", circuit);
+ let result = circuit.get(1, 1).gate;
+ expect(result.symbol).toBe('U');
+ expect(result.parameters['phi']).toBe(3.93);
+ expect(result.parameters['theta']).toBe(2.24);
+ expect(result.parameters['lambda']).toBe(5.12);
+ })
+ //check that too many parameters results in a failed set operation
+ test("Check that the input 'u(1, [1], 1, 2, 3, 4)' does NOT result creation of a unitary gate", ()=>{
+ let circuit = quantumjs.Q(4, 4);
+ runner.evaluateInput("u(1, [1], 1, 2, 3, 4)", circuit);
+ expect(circuit.get(1, [1])).toBeUndefined();
+ })
+ test("Check that the input 'u(1, [1], 1, 2)' does result creation of a unitary gate with phi=1,theta=2,lambda=[default]", ()=>{
+ let circuit = quantumjs.Q(4, 4);
+ runner.evaluateInput("u(1, [1], 1, 2)", circuit);
+ let result = circuit.get(1, 1).gate;
+ let defaultUnitary = quantumjs.Gate.findBySymbol('U');
+ expect(result.symbol).toBe('U');
+ expect(result.parameters['phi']).toBe(1);
+ expect(result.parameters['theta']).toBe(2);
+ expect(result.parameters['lambda']).toBe(defaultUnitary.parameters['lambda']);
+ })
+})
+
+
+describe("Test various combinations of set and remove operations chained together", ()=> {
+ test("Check that the input 'h(1, [1]).x(2, [1])' results in valid gate set operations", ()=> {
+ let circuit = quantumjs.Q(4, 4);
+ runner.evaluateInput("h(1, [1]).x(2, [1])", circuit);
+ expect(circuit.get(1, 1).gate.symbol).toBe('H');
+ expect(circuit.get(2, 1).gate.symbol).toBe('X');
+ })
+ //Messing with the whitespace
+ test("Check that the input 'h(1, [ 1 ]).x( 2 , [ 1 ] )' results in valid gate set operations", ()=> {
+ let circuit = quantumjs.Q(4, 4);
+ runner.evaluateInput("h(1, [ 1 ]).x( 2 , [ 1 ] )", circuit);
+ expect(circuit.get(1, 1).gate.symbol).toBe('H');
+ expect(circuit.get(2, 1).gate.symbol).toBe('X');
+ })
+ test("Check that the input 'h(1, [1]).x(2, [1, 2]).p(1, [3], 3.14159)' results in valid gate set operations", ()=> {
+ let circuit = quantumjs.Q(4, 4);
+ runner.evaluateInput("h(1, [1]).x(2, [1, 2]).p(1, [3], 3.14159)", circuit);
+ expect(circuit.get(1, 1).gate.symbol).toBe('H');
+ expect(circuit.get(2, 1).gate.symbol).toBe('X');
+ expect(circuit.get(2, 1).registerIndices).toEqual([1, 2]);
+ expect(circuit.get(1, 3).gate.symbol).toBe('P');
+ expect(circuit.get(1, 3).gate.parameters['phi']).toBe(3.14159);
+ })
+ test("Check that the input 'h(1, [1]).x(2, [1, 2]).remove(1, [1]).p(1, [3], 3.14159)' results in valid gate set operations", ()=> {
+ let circuit = quantumjs.Q(4, 4);
+ runner.evaluateInput("h(1, [1]).x(2, [1, 2]).remove(1, [1]).p(1, [3], 3.14159)", circuit);
+ expect(circuit.get(1, 1)).toBeUndefined();
+ expect(circuit.get(2, 1).gate.symbol).toBe('X');
+ expect(circuit.get(2, 1).registerIndices).toEqual([1, 2]);
+ expect(circuit.get(1, 3).gate.symbol).toBe('P');
+ expect(circuit.get(1, 3).gate.parameters['phi']).toBe(3.14159);
+ })
+})
+
+
+describe.only("Check that the prepareCircuit() method correctly creates circuits based on user input", ()=> {
+ let prompt = jest.fn();
+ test("Test prepareCircuit() with inputs '1'...'4' at prompts to see it creates an empty circuit with bandwidth = 4", ()=> {
+ prompt.mockReturnValueOnce("1").mockReturnValueOnce("4");
+ let circuit = runner.prepareCircuit(prompt);
+ expect(circuit instanceof quantumjs.Circuit).toBeTruthy();
+ expect(circuit.bandwidth).toBe(4);
+ expect(circuit.timewidth).toBe(8);
+ //check that there are no operations on the circuit
+ for(let i = 0; i < circuit.bandwidth; i++) {
+ for(let j = 0; j < circuit.timewidth; j++) {
+ expect(circuit.get(i, j)).toBeUndefined();
+ }
+ }
+ })
+ test("Test prepareCircuit() with inputs '1'...'-1'...'4' at prompts to see it creates an empty circuit with bandwidth = 4", ()=> {
+ //the -1 input will not trigger a circuit creation as -1 is not a valid number of registers. The user will be prompted again, to which '4' will be a valid response.
+ prompt.mockReturnValueOnce("1").mockReturnValueOnce('-1').mockReturnValueOnce("4");
+ let circuit = runner.prepareCircuit(prompt);
+ expect(circuit instanceof quantumjs.Circuit).toBeTruthy();
+ expect(circuit.bandwidth).toBe(4);
+ expect(circuit.timewidth).toBe(8);
+ //check that there are no operations on the circuit
+ for(let i = 0; i < circuit.bandwidth; i++) {
+ for(let j = 0; j < circuit.timewidth; j++) {
+ expect(circuit.get(i, j)).toBeUndefined();
+ }
+ }
+ })
+})
\ No newline at end of file
diff --git a/packages/quantum-js-cli/index.js b/packages/quantum-js-cli/index.js
new file mode 100644
index 0000000..a28abe7
--- /dev/null
+++ b/packages/quantum-js-cli/index.js
@@ -0,0 +1,3 @@
+const {run} = require('./runner');
+
+run();
diff --git a/packages/quantum-js-cli/package.json b/packages/quantum-js-cli/package.json
new file mode 100644
index 0000000..f30a8c7
--- /dev/null
+++ b/packages/quantum-js-cli/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "quantum-js-cli",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "jest"
+ },
+ "dependencies": {
+ "prompt-sync": "^4.2.0",
+ "readline-sync": "^1.4.10"
+ },
+ "author": "",
+ "license": "ISC"
+}
diff --git a/packages/quantum-js-cli/runner.js b/packages/quantum-js-cli/runner.js
new file mode 100644
index 0000000..4267b5d
--- /dev/null
+++ b/packages/quantum-js-cli/runner.js
@@ -0,0 +1,228 @@
+const {Q, Circuit, Gate, logger} = require('quantum-js-util');
+const prompt_sync = require('prompt-sync')({sigint: true});
+var readlineSync = require('readline-sync');
+const mark = "> ";
+
+
+function prepareCircuit(prompt = prompt_sync) {
+ let selection = NaN;
+ console.clear();
+ let circuit;
+ while(!selection) {
+ //the following prompt requires the user to select between a number of options to create a circuit. they will enter the NUMBER that corresponds with the action they'd like.
+ console.log("Please select a method (number value) to begin circuit creation: ");
+ console.log("1. From Scratch\n" +
+ "2. From Text Diagram\n");
+ selection = Number(prompt(mark));
+ switch(selection) {
+ case 1:
+ let num_registers = NaN;
+ while(!num_registers || num_registers <= 0) {
+ console.log("Enter the number of qubits you would like to start out with.\n");
+ num_registers = Number(prompt(mark));
+ }
+ circuit = Q(num_registers, 8);
+ break;
+ case 2:
+ circuit = prepareCircuitFromTable();
+ break;
+ default:
+ selection = NaN;
+ }
+ }
+ if(!(circuit instanceof Circuit)) {
+ logger.error("Failed to create circuit");
+ process.exit();
+ }
+ console.log(circuit.toDiagram());
+ return circuit;
+}
+
+function prepareCircuitFromTable() {
+ let resultingCircuit;
+ let tableString;
+ let lines = [];
+ console.log('Input (or paste) your table below and press [Enter] key twice to submit.');
+ readlineSync.promptLoop(line => {
+ lines.push(line);
+ return !line;
+ }, {prompt: ''});
+ tableString = lines.join('\n');
+ try {
+ resultingCircuit = Q(tableString.trim());
+ }
+ catch(e) {
+ return logger.error("Failed to create circuit from table.");
+ }
+ if(!(resultingCircuit instanceof Circuit) || resultingCircuit.bandwidth <= 0 || resultingCircuit.timewidth <= 0) return logger.error("Failed to create circuit from table.");
+ return resultingCircuit;
+}
+
+
+
+function printMenu() {
+ let menu =
+`-h, help Print Q.js command line options (currently set)
+ toDiagram Print the current circuit as a text diagram
+ toAmazonBraket Export the current circuit as Python code using the Amazon Braket SDK.
+ toLaTeX Print the current circuit as a LaTeX diagram
+ toText Print as a table using only common characters (can be used to import later).
+ report Evaluate and current circuit's probability report
+ clear Clear the console
+ newCircuit Discard the current circuit and create a new circuit.
+q, quit Exit the command line
+ `;
+ console.log(menu);
+ return menu;
+}
+
+function evaluateOperation(input, circuit) {
+ let functionName = (/[^\s,\[\]()]+/g).exec(input)[0];
+ let gate = Gate.findBySymbol(functionName);
+ if(!gate) gate = Gate.findByNameCss(functionName)
+ //checks that the function call is gate set$ operation and not another circuit operation.
+ //Syntax: GateSymbol(momentIndex, [registerIndex0, registerIndex1,...])
+ //Regex explanation: looks for the first INTEGER of the from "(digit0digit1digit2..." and removes the "(" at the beginning.
+ let momentIndex = +(/\(\s*\d+/).exec(input)[0].substring(1);
+ if(momentIndex > circuit.timewidth || momentIndex < 0) return logger.error("Moment index out of bounds");
+ if(momentIndex === undefined) {
+ return logger.error("Invalid gate set operation syntax");
+ }
+
+ let registerIndices;
+ //This is a regex that selects an array of integers from a string, i.e. any substring of the form "[integer1, integer2, integer3...]"
+ let arrayRegex = /\[(\s*\d+\s*,{0,1}\s*)+\]/g;
+ try {
+ registerIndices = (arrayRegex)
+ .exec(input)[0]
+ .slice(1, -1)
+ .split(',')
+ .map(index => +index);
+ }
+ catch(e) {
+ return logger.error("Invalid gate set operation syntax");
+ }
+ if(!registerIndices.every(index => {
+ return index > 0 && index <= circuit.bandwidth;
+ })) return logger.error("Register index out of bounds");
+ let newParameters = {};
+ input = input.substring(arrayRegex.lastIndex);
+ let commaSeparatedDecimalRegex = /\d+\.{0,1}\d*/g
+ let input_params = [];
+ while(value = commaSeparatedDecimalRegex.exec(input)) {
+ input_params.push(Number(value[0]));
+ }
+ input_params.reverse();
+ if(gate.has_parameters) {
+ if(input_params.length > Object.keys(gate.parameters).length) return logger.error("b Invalid gate set operation syntax");
+ Object.keys(gate.parameters).forEach(key => {
+ newParameters[key] = input_params.pop();
+ if(!newParameters[key]) {
+ newParameters[key] = gate.parameters[key];
+ }
+ });
+ }
+ else if(input_params.length !== 0) return logger.error("Invalid gate set operation syntax");
+ return circuit[functionName](momentIndex, registerIndices, newParameters);
+}
+
+
+function removeOperation(input, circuit) {
+ let momentIndex = +(/\(\s*\d+/).exec(input)[0].substring(1);
+ if(momentIndex === undefined) {
+ return logger.error("Invalid gate set operation syntax");
+ }
+ //
+ let registerIndices;
+ let arrayRegex = /\[(\s*\d+\s*,{0,1}\s*)+\]/g;
+ try {
+ registerIndices = (arrayRegex)
+ .exec(input)[0]
+ .slice(1, -1)
+ .split(',')
+ .map(index => Number(index));
+ }
+ catch(e) {
+ return logger.error("Invalid gate set operation syntax");
+ }
+ if(input.substring(arrayRegex.lastIndex).trim() != ")") {
+ return logger.error("Invalid gate set operation syntax");
+ }
+ let operationToRemove = circuit.get(momentIndex, registerIndices[0]);
+ if(!operationToRemove) {
+ return logger.log("No operation to remove");
+ }
+ if(registerIndices.every(index => {
+ return operationToRemove.registerIndices.includes(index);
+ })) return circuit.clear$(momentIndex, operationToRemove.registerIndices);
+}
+
+function evaluateInput(input, circuit, prompt=prompt_sync) {
+ switch(input) {
+ case "-h":
+ case "help":
+ printMenu();
+ break;
+ case "toDiagram":
+ console.log(circuit.toDiagram());
+ break;
+ case "toAmazonBraket":
+ console.log(circuit.toAmazonBraket());
+ break;
+ case "toLaTeX":
+ console.log(circuit.toLatex());
+ break;
+ case "report":
+ circuit.evaluate$();
+ console.log(circuit.report$());
+ break;
+ case "clear":
+ console.clear();
+ break;
+ case "toText":
+ console.log(circuit.toText(true));
+ break;
+ case "newCircuit":
+ let response = prompt("Creating a new circuit will discard the current circuit. Enter yes to continue: ").toLowerCase();
+ if(response !== "yes") console.log("Did not create new circuit.");
+ else circuit = prepareCircuit();
+ break;
+ default:
+ let circuitBackup = circuit.toText();
+ let functionCallRegex = /((\w+-*)+\(\s*\d+\s*,\s*\[(\s*\d+\s*,{0,1}\s*)+\]\s*(,\s*\d+\.{0,1}\d*\s*)*\))/g;
+ while(functionCall = functionCallRegex.exec(input)) {
+ functionCall = functionCall[0];
+ let functionName = (/[^\s,\[\]()]+/g).exec(functionCall)[0];
+ //checks that the function call is gate set$ operation and not another circuit operation.
+ //Syntax: GateSymbol(momentIndex, [registerIndex0, registerIndex1,...])
+ if(circuit[functionName] instanceof Function && (Gate.findBySymbol(functionName) instanceof Gate || Gate.findByNameCss(functionName) instanceof Gate)) {
+ if(evaluateOperation(functionCall, circuit) === "(error)") {
+ circuit = Q(circuitBackup);
+ break;
+ }
+ }
+ //Syntax: clear(momentIndex, registerIndex)
+ //If the registerIndex is the index of a multiqubit operation, we clear all indices associated with the operation under registerIndex
+ else if(functionName == "remove") {
+ if(removeOperation(functionCall, circuit) === "(error)") {
+ circuit = circuitBackup;
+ break;
+ }
+ }
+ }
+ }
+ return circuit;
+}
+
+
+function run(prompt = prompt_sync) {
+ let circuit = prepareCircuit(prompt);
+ let input = prompt(mark);
+ while(input !== "quit" && input !== "q" && circuit !== '(error)') {
+ circuit = evaluateInput(input, circuit, prompt);
+ input = prompt(mark);
+ }
+
+}
+
+module.exports = {run, evaluateInput, removeOperation, evaluateOperation, printMenu, prepareCircuit};
\ No newline at end of file
diff --git a/packages/quantum-js-util/Misc.js b/packages/quantum-js-util/Misc.js
index 4eb824d..6c7974b 100644
--- a/packages/quantum-js-util/Misc.js
+++ b/packages/quantum-js-util/Misc.js
@@ -1,18 +1,23 @@
const logger = require('./Logging');
-const COLORS = [];
-const ANIMALS = [];
const constants = {};
-
-function dispatchEventToGlobal(event) {
- if(typeof window != undefined) {
- window.dispatchEvent(event);
- }
- else {
- //if window does exist, global == window is true. So maybe we can just do global.dispatchEvent instead of this wrapper?
- global.dispatchEvent(event);
- console.log(event);
+function dispatchCustomEventToGlobal(event_name, detail, terminate_on_error=false, silent=true) {
+ try {
+ const event = new CustomEvent(event_name, detail);
+ if(typeof window != undefined) {
+ window.dispatchEvent(event);
+ }
+ else {
+ //if window does exist, global == window is true. So maybe we can just do global.dispatchEvent instead of this wrapper?
+ global.dispatchEvent(event);
+ if(!silent) console.log(event);
+ }
+ } catch(e) {
+ //When running in node, CustomEvent and documents don't exist. We can emulate using a JSDOM package
+ if(!silent) logger.error("Could not dispatch custom event.");
+ if(terminate_on_error) process.exit();
}
+
}
function createConstant(key, value) {
@@ -60,7 +65,6 @@ function shuffleNames$() {
function getRandomName$() {
if (shuffledNames.length === 0) shuffleNames$();
-
const pair = shuffledNames[namesIndex],
name = COLORS[pair[0]] + " " + ANIMALS[pair[1]];
@@ -397,4 +401,4 @@ createConstants(
]
);
-module.exports = { createConstant, createConstants, getRandomName$, hueToColorName, colorIndexToHue, dispatchEventToGlobal, constants };
+module.exports = { createConstant, createConstants, getRandomName$, hueToColorName, colorIndexToHue, dispatchCustomEventToGlobal, constants };
diff --git a/packages/quantum-js-util/Q-Circuit.js b/packages/quantum-js-util/Q-Circuit.js
index 9cba24c..b9222a7 100644
--- a/packages/quantum-js-util/Q-Circuit.js
+++ b/packages/quantum-js-util/Q-Circuit.js
@@ -425,13 +425,13 @@ Object.assign( Circuit, {
// console.log( circuit.toDiagram() )
- misc.dispatchEventToGlobal(new CustomEvent(
+ misc.dispatchCustomEventToGlobal(
'Circuit.evaluate began', {
detail: { circuit }
}
- ))
+ );
// Our circuit’s operations must be in the correct order
@@ -541,7 +541,7 @@ Object.assign( Circuit, {
const progress = operationsCompleted / operationsTotal
- misc.dispatchEventToGlobal(new CustomEvent( 'Circuit.evaluate progressed', { detail: {
+ misc.dispatchCustomEventToGlobal('Circuit.evaluate progressed', { detail: {
circuit,
progress,
@@ -552,7 +552,7 @@ Object.assign( Circuit, {
gate: operation.gate.name,
state
- }}))
+ }})
// console.log( `\n\nProgress ... ${ Math.round( operationsCompleted / operationsTotal * 100 )}%`)
@@ -591,13 +591,13 @@ Object.assign( Circuit, {
- misc.dispatchEventToGlobal(new CustomEvent( 'Circuit.evaluate completed', { detail: {
+ misc.dispatchCustomEventToGlobal('Circuit.evaluate completed', { detail: {
// circuit.dispatchEvent( new CustomEvent( 'evaluation complete', { detail: {
circuit,
results: outcomes
- }}))
+ }})
@@ -1388,7 +1388,7 @@ print(task.result().measurement_counts)`
foundOperations.forEach( function( operation ){
- misc.dispatchEventToGlobal(new CustomEvent(
+ misc.dispatchCustomEventToGlobal(
'Circuit.clear$', { detail: {
@@ -1396,7 +1396,7 @@ print(task.result().measurement_counts)`
momentIndex,
registerIndices: operation.registerIndices
}}
- ))
+ )
})
}
@@ -1545,14 +1545,14 @@ print(task.result().measurement_counts)`
// Emit an event that we have set an operation
// on this circuit.
- misc.dispatchEventToGlobal(new CustomEvent(
+ misc.dispatchCustomEventToGlobal(
'Circuit.set$', { detail: {
circuit,
operation
}}
- ))
+ )
}
return circuit
},
@@ -1615,16 +1615,15 @@ print(task.result().measurement_counts)`
const original = this
let {
- registerFirstIndex,
- registerRange,
- registerLastIndex,
+ qubitFirstIndex,
+ qubitRange,
+ qubitLastIndex,
momentFirstIndex,
momentRange,
momentLastIndex
} = this.determineRanges( options )
-
- const copy = new Circuit( registerRange, momentRange )
+ const copy = new Circuit( qubitRange, momentRange )
original.operations
.filter( function( operation ){
@@ -1635,8 +1634,8 @@ print(task.result().measurement_counts)`
operation.momentIndex >= momentFirstIndex &&
operation.momentIndex < momentLastIndex &&
- operation.registerIndex >= registerFirstIndex &&
- operation.registerIndex < registerLastIndex
+ operation.registerIndex >= qubitFirstIndex &&
+ operation.registerIndex < qubitLastIndex
)
}))
})
@@ -1943,12 +1942,15 @@ Object.entries( Gate.constants ).forEach( function( entry ){
const
gateConstantName = entry[ 0 ],
gate = entry[ 1 ],
- set$ = function( momentIndex, registerIndexOrIndices ){
+ set$ = function( momentIndex, registerIndexOrIndices, parameters ){
- this.set$( gate, momentIndex, registerIndexOrIndices )
+ this.set$( gate, momentIndex, registerIndexOrIndices, parameters )
return this
}
- Circuit.prototype[ gateConstantName ] = set$
+ Circuit.prototype[ gate.name ] = set$,
+ Circuit.prototype[ gate.name.toLowerCase() ] = set$,
+ Circuit.prototype[ gate.nameCss ] = set$,
+ Circuit.prototype[ gate.nameCss.toLowerCase() ] = set$,
Circuit.prototype[ gate.symbol ] = set$
Circuit.prototype[ gate.symbol.toLowerCase() ] = set$
})
diff --git a/packages/quantum-js-util/Q-Gate.js b/packages/quantum-js-util/Q-Gate.js
index 472dde1..488a666 100644
--- a/packages/quantum-js-util/Q-Gate.js
+++ b/packages/quantum-js-util/Q-Gate.js
@@ -11,7 +11,6 @@ Gate = function( params ){
this.index = Gate.index ++
if( typeof this.symbol !== 'string' ) this.symbol = '?'
- if( typeof this.symbolAmazonBraket !== 'string' ) this.symbolAmazonBraket = this.symbol.toLowerCase()
const parameters = Object.assign( {}, params.parameters )
this.parameters = parameters
@@ -99,6 +98,9 @@ Object.assign( Gate, {
findByName: function( name ){
return Gate.findBy( 'name', name )
+ },
+ findByNameCss: function( nameCss ) {
+ return Gate.findBy( 'nameCss', nameCss )
}
})
diff --git a/packages/quantum-js-util/Q-History.js b/packages/quantum-js-util/Q-History.js
index 1d38945..a41cfaf 100644
--- a/packages/quantum-js-util/Q-History.js
+++ b/packages/quantum-js-util/Q-History.js
@@ -1,7 +1,7 @@
// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
-const {dispatchEventToGlobal} = require('./Misc');
+const {dispatchCustomEventToGlobal} = require('./Misc');
History = function( instance ){
@@ -26,31 +26,27 @@ Object.assign( History.prototype, {
const instance = this.instance
if( this.index > 0 ){
- dispatchEventToGlobal(new CustomEvent(
-
+ dispatchCustomEventToGlobal(
'History undo is capable', { detail: { instance }}
- ));
+ );
}
else {
- dispatchEventToGlobal(new CustomEvent(
-
+ dispatchCustomEventToGlobal(
'History undo is depleted', { detail: { instance }}
- ))
+ )
}
if( this.index + 1 < this.entries.length ){
- dispatchEventToGlobal(new CustomEvent(
-
+ dispatchCustomEventToGlobal(
'History redo is capable', { detail: { instance }}
- ))
+ )
}
else {
- dispatchEventToGlobal(new CustomEvent(
-
+ dispatchCustomEventToGlobal(
'History redo is depleted', { detail: { instance }}
- ))
+ )
}
return this
},
diff --git a/packages/quantum-js-util/Q-Matrix.js b/packages/quantum-js-util/Q-Matrix.js
index 6f837c5..544b4d4 100644
--- a/packages/quantum-js-util/Q-Matrix.js
+++ b/packages/quantum-js-util/Q-Matrix.js
@@ -96,7 +96,7 @@ Matrix = function () {
Object.assign(Matrix, {
index: 0,
help: function () {
- return help(this);
+ return logger.help(this);
},
constants: {}, // Only holds references; an easy way to look up what constants exist.
createConstant: function (key, value) {
diff --git a/packages/quantum-js-util/Q-Qubit.js b/packages/quantum-js-util/Q-Qubit.js
index 48c3f1e..99fc7c4 100644
--- a/packages/quantum-js-util/Q-Qubit.js
+++ b/packages/quantum-js-util/Q-Qubit.js
@@ -95,7 +95,7 @@ Qubit.prototype.constructor = Qubit;
Object.assign(Qubit, {
index: 0,
help: function () {
- return help(this);
+ return logger.help(this);
},
constants: {},
createConstant: function (key, value) {
diff --git a/packages/quantum-js-util/Q.js b/packages/quantum-js-util/Q.js
index a4ca1d5..4749fb2 100644
--- a/packages/quantum-js-util/Q.js
+++ b/packages/quantum-js-util/Q.js
@@ -1,25 +1,19 @@
// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
-const logger = require('./Logging');
const misc = require('./Misc');
const mathf = require('./Math-Functions');
-const {ComplexNumber} = require('./Q-ComplexNumber');
-const {Gate} = require('./Q-Gate');
-const {Qubit} = require('./Q-Qubit');
-const {Matrix} = require('./Q-Matrix');
-const {History} = require('./Q-History');
const {Circuit} = require('./Q-Circuit');
-const Q = function () {
+Q = function () {
// Did we send arguments of the form
// ( bandwidth, timewidth )?
if (
arguments.length === 2 &&
Array.from(arguments).every(function (argument) {
- return isUsefulInteger(argument);
+ return mathf.isUsefulInteger(argument);
})
) {
return new Circuit(arguments[0], arguments[1]);
@@ -51,5 +45,5 @@ https://quantumjavascript.app
`);
-module.exports = {logger, misc, mathf, ComplexNumber, Matrix, Gate, Qubit, History, Circuit, Q};
+module.exports = {Q};
diff --git a/packages/quantum-js-util/index.js b/packages/quantum-js-util/index.js
index c1cc69f..997c0c9 100644
--- a/packages/quantum-js-util/index.js
+++ b/packages/quantum-js-util/index.js
@@ -1 +1,12 @@
-const {Q} = require('./Q');
+const logger = require('./Logging');
+const misc = require('./Misc');
+const mathf = require('./Math-Functions');
+const {ComplexNumber} = require('./Q-ComplexNumber');
+const {Gate} = require('./Q-Gate');
+const {Qubit} = require('./Q-Qubit');
+const {Matrix} = require('./Q-Matrix');
+const {History} = require('./Q-History');
+const {Circuit} = require('./Q-Circuit');
+const {Q} = require('./Q.js');
+
+module.exports = {logger, misc, mathf, ComplexNumber, Matrix, Gate, Qubit, History, Circuit, Q};
diff --git a/packages/quantum-js-util/package.json b/packages/quantum-js-util/package.json
index 55c7fe1..3587052 100644
--- a/packages/quantum-js-util/package.json
+++ b/packages/quantum-js-util/package.json
@@ -2,7 +2,7 @@
"name": "quantum-js-util",
"version": "1.0.0",
"description": "",
- "main": "Q.js",
+ "main": "index.js",
"scripts": {
"test": "jest",
"prettier": "echo 'I am a prettier util!' && exit 0"
diff --git a/packages/quantum-js-vis/Q-BlochSphere.js b/packages/quantum-js-vis/Q-BlochSphere.js
new file mode 100644
index 0000000..a1ff2e5
--- /dev/null
+++ b/packages/quantum-js-vis/Q-BlochSphere.js
@@ -0,0 +1,582 @@
+
+// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+
+
+
+const {Qubit} = require('quantum-js-util');
+BlochSphere = function( onValueChange ){
+
+ Object.assign( this, {
+
+ isRotating: false,
+ radius: 1,
+ radiusSafe: 1.01,
+ axesLineWidth: 0.01,
+ arcLineWidth: 0.015,
+ state: Qubit.LEFT_HAND_CIRCULAR_POLARIZED.toBlochSphere(),
+ target: Qubit.HORIZONTAL.toBlochSphere(),
+ group: new THREE.Group(),
+ onValueChange
+ })
+
+
+ // Create the surface of the Bloch sphere.
+
+ const surface = new THREE.Mesh(
+
+ new THREE.SphereGeometry( this.radius, 64, 64 ),
+ new THREE.MeshPhongMaterial({
+
+ side: THREE.FrontSide,
+ map: BlochSphere.makeSurface(),
+ transparent: true,
+ opacity: 0.97
+ })
+ )
+ surface.receiveShadow = true
+ this.group.add( surface )
+
+
+
+
+ // Create the X, Y, and Z axis lines.
+
+ const
+ xAxis = new THREE.Mesh(
+
+ new THREE.BoxGeometry( this.axesLineWidth, this.axesLineWidth, this.radius * 2.5 ),
+ new THREE.MeshBasicMaterial({ color: BlochSphere.xAxisColor })
+ ),
+ yAxis = new THREE.Mesh(
+
+ new THREE.BoxGeometry( this.radius * 2.5, this.axesLineWidth, this.axesLineWidth ),
+ new THREE.MeshBasicMaterial({ color: BlochSphere.yAxisColor })
+ ),
+ zAxis = new THREE.Mesh(
+
+ new THREE.BoxGeometry( this.axesLineWidth, this.radius * 2.5, this.axesLineWidth ),
+ new THREE.MeshBasicMaterial({ color: BlochSphere.zAxisColor })
+ )
+
+ this.group.add( xAxis, yAxis, zAxis )
+
+
+ // Create X, Y, and Z arrow heads,
+ // indicating positive directions for all three.
+
+ const
+ arrowLength = 0.101,// I know, weird, right?
+ arrowHeadLength = 0.1,
+ arrowHeadWidth = 0.1
+
+ this.group.add( new THREE.ArrowHelper(
+
+ new THREE.Vector3( 0, 0, 1.00 ),
+ new THREE.Vector3( 0, 0, 1.25 ),
+ arrowLength,
+ BlochSphere.xAxisColor,// Red
+ arrowHeadLength,
+ arrowHeadWidth
+ ))
+ this.group.add( new THREE.ArrowHelper(
+
+ new THREE.Vector3( 1.00, 0, 0 ),
+ new THREE.Vector3( 1.25, 0, 0 ),
+ arrowLength,
+ BlochSphere.yAxisColor,// Green
+ arrowHeadLength,
+ arrowHeadWidth
+ ))
+ this.group.add( new THREE.ArrowHelper(
+
+ new THREE.Vector3( 0, 1.00, 0 ),
+ new THREE.Vector3( 0, 1.25, 0 ),
+ arrowLength,
+ BlochSphere.zAxisColor,// Blue
+ arrowHeadLength,
+ arrowHeadWidth
+ ))
+
+
+ // Create the X, Y, and Z axis labels.
+
+ const
+ axesLabelStyle = {
+
+ width: 128,
+ height: 128,
+ fillStyle: BlochSphere.vectorColor,//'#505962',
+ font: 'bold italic 64px Georgia, "Times New Roman", serif'
+ },
+ xAxisLabel = new THREE.Sprite(
+
+ new THREE.SpriteMaterial({
+
+ map: Object.assign( SurfaceText( axesLabelStyle ))
+ })
+ ),
+ yAxisLabel = new THREE.Sprite(
+
+ new THREE.SpriteMaterial({
+
+ map: Object.assign( SurfaceText( axesLabelStyle ))
+ })
+ ),
+ zAxisLabel = new THREE.Sprite(
+
+ new THREE.SpriteMaterial({
+
+ map: Object.assign( SurfaceText( axesLabelStyle ))
+ })
+ )
+
+ xAxisLabel.material.map.print( 'x' )
+ xAxisLabel.position.set( 0, 0, 1.45 )
+ xAxisLabel.scale.set( 0.25, 0.25, 0.25 )
+ xAxis.add( xAxisLabel )
+
+ yAxisLabel.material.map.print( 'y' )
+ yAxisLabel.position.set( 1.45, 0, 0 )
+ yAxisLabel.scale.set( 0.25, 0.25, 0.25 )
+ yAxis.add( yAxisLabel )
+
+ zAxisLabel.material.map.print( 'z' )
+ zAxisLabel.position.set( 0, 1.45, 0 )
+ zAxisLabel.scale.set( 0.25, 0.25, 0.25 )
+ zAxis.add( zAxisLabel )
+
+
+ this.blochColor = new THREE.Color()
+
+
+ // Create the line from the sphere’s origin
+ // out to where the Bloch vector intersects
+ // with the sphere’s surface.
+
+ this.blochVector = new THREE.Mesh(
+
+ new THREE.BoxGeometry( 0.04, 0.04, this.radius ),
+ new THREE.MeshBasicMaterial({ color: BlochSphere.vectorColor })
+ )
+ this.blochVector.geometry.translate( 0, 0, 0.5 )
+ this.group.add( this.blochVector )
+
+
+ // Create the cone that indicates the Bloch vector
+ // and points to where that vectors
+ // intersects with the surface of the sphere.
+
+ this.blochPointer = new THREE.Mesh(
+
+ new THREE.CylinderBufferGeometry( 0, 0.5, 1, 32, 1 ),
+ new THREE.MeshPhongMaterial({ color: BlochSphere.vectorColor })
+ )
+ this.blochPointer.geometry.translate( 0, -0.5, 0 )
+ this.blochPointer.geometry.rotateX( Math.PI / 2 )
+ this.blochPointer.geometry.scale( 0.2, 0.2, 0.2 )
+ this.blochPointer.lookAt( new THREE.Vector3() )
+ this.blochPointer.receiveShadow = true
+ this.blochPointer.castShadow = true
+ this.group.add( this.blochPointer )
+
+
+ // Create the Theta ring that will belt the sphere.
+
+ const
+ arcR = this.radiusSafe * Math.sin( Math.PI / 2 ),
+ arcH = this.radiusSafe * Math.cos( Math.PI / 2 ),
+ thetaGeometry = BlochSphere.createLatitudeArc( arcR, 128, Math.PI / 2, Math.PI * 2 ),
+ thetaLine = new MeshLine(),
+ thetaPhiMaterial = new MeshLineMaterial({
+
+ color: BlochSphere.thetaPhiColor,//0x505962,
+ lineWidth: this.arcLineWidth * 3,
+ sizeAttenuation: true
+ })
+
+ thetaGeometry.rotateX( Math.PI / 2 )
+ thetaGeometry.rotateY( Math.PI / 2 )
+ thetaGeometry.translate( 0, arcH, 0 )
+ thetaLine.setGeometry( thetaGeometry )
+
+ this.thetaMesh = new THREE.Mesh(
+
+ thetaLine.geometry,
+ thetaPhiMaterial
+ )
+ this.group.add( this.thetaMesh )
+
+
+ // Create the Phi arc that will draw from the north pole
+ // down to wherever the Theta arc rests.
+
+ this.phiGeometry = BlochSphere.createLongitudeArc( this.radiusSafe, 64, 0, Math.PI * 2 ),
+ this.phiLine = new MeshLine()
+ this.phiLine.setGeometry( this.phiGeometry )
+ this.phiMesh = new THREE.Mesh(
+
+ this.phiLine.geometry,
+ thetaPhiMaterial
+ )
+ this.group.add( this.phiMesh )
+
+
+
+
+ // Time to put plans to action.
+
+ BlochSphere.prototype.setTargetState.call( this )
+}
+
+
+
+
+
+
+ ////////////////
+ // //
+ // Static //
+ // //
+////////////////
+
+
+Object.assign( BlochSphere, {
+
+ xAxisColor: 0x333333,// Was 0xCF1717 (red)
+ yAxisColor: 0x333333,// Was 0x59A112 (green)
+ zAxisColor: 0x333333,// Was 0x0F66BD (blue)
+ vectorColor: 0xFFFFFF,// Was 0xF2B90D (yellow)
+ thetaPhiColor: 0x333333,// Was 0xF2B90D (yellow)
+
+
+ // It’s important that we build the texture
+ // right here and now, rather than load an image.
+ // Why? Because if we load a pre-existing image
+ // we run into CORS problems using file:/// !
+
+ makeSurface: function(){
+
+ const
+ width = 2048,
+ height = width / 2
+
+ const canvas = document.createElement( 'canvas' )
+ canvas.width = width
+ canvas.height = height
+
+ const context = canvas.getContext( '2d' )
+ context.fillStyle = 'hsl( 210, 20%, 100% )'
+ context.fillRect( 0, 0, width, height )
+
+
+ // Create the base hue gradient for our texture.
+
+ const
+ hueGradient = context.createLinearGradient( 0, height / 2, width, height / 2 ),
+ hueSteps = 180,
+ huesPerStep = 360 / hueSteps
+
+ for( let i = 0; i <= hueSteps; i ++ ){
+
+ hueGradient.addColorStop( i / hueSteps, 'hsl( '+ ( i * huesPerStep - 90 ) +', 100%, 50% )' )
+ }
+ context.fillStyle = hueGradient
+ context.fillRect( 0, 0, width, height )
+
+
+ // For both the northern gradient (to white)
+ // and the southern gradient (to black)
+ // we’ll leave a thin band of full saturation
+ // near the equator.
+
+ const whiteGradient = context.createLinearGradient( width / 2, 0, width / 2, height / 2 )
+ whiteGradient.addColorStop( 0.000, 'hsla( 0, 0%, 100%, 1 )' )
+ whiteGradient.addColorStop( 0.125, 'hsla( 0, 0%, 100%, 1 )' )
+ whiteGradient.addColorStop( 0.875, 'hsla( 0, 0%, 100%, 0 )' )
+ context.fillStyle = whiteGradient
+ context.fillRect( 0, 0, width, height / 2 )
+
+ const blackGradient = context.createLinearGradient( width / 2, height / 2, width / 2, height )
+ blackGradient.addColorStop( 0.125, 'hsla( 0, 0%, 0%, 0 )' )
+ blackGradient.addColorStop( 0.875, 'hsla( 0, 0%, 0%, 1 )' )
+ blackGradient.addColorStop( 1.000, 'hsla( 0, 0%, 0%, 1 )' )
+ context.fillStyle = blackGradient
+ context.fillRect( 0, height / 2, width, height )
+
+
+ // Create lines of latitude and longitude.
+ // Note this is an inverse Mercatur projection ;)
+
+ context.fillStyle = 'hsla( 0, 0%, 0%, 0.2 )'
+ const yStep = height / 16
+ for( let y = 0; y <= height; y += yStep ){
+
+ context.fillRect( 0, y, width, 1 )
+ }
+ const xStep = width / 16
+ for( let x = 0; x <= width; x += xStep ){
+
+ context.fillRect( x, 0, 1, height )
+ }
+
+
+ // Prepare the THREE texture and return it
+ // so we can use it as a material map.
+
+ const texture = new THREE.CanvasTexture( canvas )
+ texture.needsUpdate = true
+ return texture
+ },
+
+
+
+
+ createLongitudeArc: function( radius, segments, thetaStart, thetaLength ){
+
+ const geometry = new THREE.CircleGeometry( radius, segments, thetaStart, thetaLength )
+ geometry.vertices.shift()
+
+
+ // This is NOT NORMALLY necessary
+ // because we expect this to only be
+ // between PI/2 and PI*2
+ // (so the length is only Math.PI instead of PI*2).
+
+ if( thetaLength >= Math.PI * 2 ){
+
+ geometry.vertices.push( geometry.vertices[ 0 ].clone() )
+ }
+ return geometry
+ },
+ createLatitudeArc: function( radius, segments, phiStart, phiLength ){
+
+ const geometry = new THREE.CircleGeometry( radius, segments, phiStart, phiLength )
+ geometry.vertices.shift()
+ if( phiLength >= 2 * Math.PI ){
+
+ geometry.vertices.push( geometry.vertices[ 0 ].clone() )
+ }
+ return geometry
+ },
+ createQuadSphere: function( options ){
+
+ let {
+
+ radius,
+ phiStart,
+ phiLength,
+ thetaStart,
+ thetaLength,
+ latitudeLinesTotal,
+ longitudeLinesTotal,
+ latitudeLineSegments,
+ longitudeLineSegments,
+ latitudeLinesAttributes,
+ longitudeLinesAttributes
+
+ } = options
+
+ if( typeof radius !== 'number' ) radius = 1
+ if( typeof phiStart !== 'number' ) phiStart = Math.PI / 2
+ if( typeof phiLength !== 'number' ) phiLength = Math.PI * 2
+ if( typeof thetaStart !== 'number' ) thetaStart = 0
+ if( typeof thetaLength !== 'number' ) thetaLength = Math.PI
+ if( typeof latitudeLinesTotal !== 'number' ) latitudeLinesTotal = 16
+ if( typeof longitudeLinesTotal !== 'number' ) longitudeLinesTotal = 16
+ if( typeof latitudeLineSegments !== 'number' ) latitudeLineSegments = 64
+ if( typeof longitudeLineSegments !== 'number' ) longitudeLineSegments = 64
+ if( typeof latitudeLinesAttributes === 'undefined' ) latitudeLinesAttributes = { color: 0xCCCCCC }
+ if( typeof longitudeLinesAttributes === 'undefined' ) longitudeLinesAttributes = { color: 0xCCCCCC }
+
+ const
+ sphere = new THREE.Group(),
+ latitudeLinesMaterial = new THREE.LineBasicMaterial( latitudeLinesAttributes ),
+ longitudeLinesMaterial = new THREE.LineBasicMaterial( longitudeLinesAttributes )
+
+
+ // Lines of longitude.
+ // https://en.wikipedia.org/wiki/Longitude
+
+ for(
+
+ let
+ phiDelta = phiLength / longitudeLinesTotal,
+ phi = phiStart,
+ arc = BlochSphere.createLongitudeArc( radius, longitudeLineSegments, thetaStart + Math.PI / 2, thetaLength );
+ phi < phiStart + phiLength + phiDelta;
+ phi += phiDelta ){
+
+ const geometry = arc.clone()
+ geometry.rotateY( phi )
+ sphere.add( new THREE.Line( geometry, longitudeLinesMaterial ))
+ }
+
+
+ // Lines of latitude.
+ // https://en.wikipedia.org/wiki/Latitude
+
+ for (
+
+ let
+ thetaDelta = thetaLength / latitudeLinesTotal,
+ theta = thetaStart;
+ theta < thetaStart + thetaLength;
+ theta += thetaDelta ){
+
+ if( theta === 0 ) continue
+
+ const
+ arcR = radius * Math.sin( theta ),
+ arcH = radius * Math.cos( theta ),
+ geometry = BlochSphere.createLatitudeArc( arcR, latitudeLineSegments, phiStart, phiLength )
+
+ geometry.rotateX( Math.PI / 2 )
+ geometry.rotateY( Math.PI / 2 )
+ geometry.translate( 0, arcH, 0 )
+ sphere.add( new THREE.Line( geometry, latitudeLinesMaterial ))
+ }
+
+
+ return sphere
+ }
+})
+
+
+
+
+
+
+ ///////////////
+ // //
+ // Proto //
+ // //
+///////////////
+
+
+Object.assign( BlochSphere.prototype, {
+
+ update: function(){
+
+ if( this.isRotating ) this.group.rotation.y += Math.PI / 4096
+ },
+ setTargetState: function( target ){
+
+ if( target === undefined ) target = Qubit.HORIZONTAL.toBlochSphere()
+
+
+ // Always take the shortest path around
+ // even if it crosses the 0˚ / 360˚ boundary,
+ // ie. between Anti-Diagonal (-90˚) and
+ // Right0-and circular polarized (180˚).
+
+ const
+ rangeHalf = Math.PI,
+ distance = this.state.phi - target.phi
+
+ if( Math.abs( distance ) > rangeHalf ){
+
+ this.state.phi += Math.sign( distance ) * rangeHalf * -2
+ }
+
+
+ // Cheap hack to test if we need to update values
+ // from within the updateBlochVector method.
+
+ Object.assign( this.target, target )
+
+
+ // Create the tween.
+
+ window.tween = new TWEEN.Tween( this.state )
+ .to( target, 1000 )
+ .easing( TWEEN.Easing.Quadratic.InOut )
+ .onUpdate( this.updateBlochVector.bind( this ))
+ .start()
+ },
+ updateBlochVector: function( state ){
+
+
+ // Move the big-ass surface pointer.
+
+ if( state.theta !== this.target.theta ||
+ state.phi !== this.target.phi ){
+
+ this.blochPointer.position.set(
+
+ Math.sin( state.theta ) * Math.sin( state.phi ),
+ Math.cos( state.theta ),
+ Math.sin( state.theta ) * Math.cos( state.phi )
+ )
+ this.blochPointer.lookAt( new THREE.Vector3() )
+ this.blochVector.lookAt( this.blochPointer.getWorldPosition( new THREE.Vector3() ))
+
+
+ // Determine the correct HSL color
+ // based on Phi and Theta.
+
+ let hue = state.phi * THREE.Math.RAD2DEG
+ if( hue < 0 ) hue = 360 + hue
+ this.blochColor.setHSL(
+
+ hue / 360,
+ 1,
+ 1 - ( state.theta / Math.PI )
+ )
+ this.blochPointer.material.color = this.blochColor
+ this.blochVector.material.color = this.blochColor
+
+ if( state.theta !== this.target.theta ){
+
+
+ // Slide the Theta ring from the north pole
+ // down as far south as it needs to go
+ // and scale its radius so it belts the sphere.
+
+ const thetaScaleSafe = Math.max( state.theta, 0.01 )
+ this.thetaMesh.scale.set(
+
+ Math.sin( thetaScaleSafe ),
+ 1,
+ Math.sin( thetaScaleSafe )
+ )
+ this.thetaMesh.position.y = Math.cos( state.theta )
+
+
+ // Redraw the Phi arc to extend from the north pole
+ // down to only as far as the Theta ring sits.
+ // Then rotate the whole Phi arc about the poles.
+
+ for(
+
+ let
+ i = 0,
+ limit = this.phiGeometry.vertices.length;
+
+ i < limit;
+ i ++ ){
+
+ const gain = i / ( limit - 1 )
+ this.phiGeometry.vertices[ i ].set(
+
+ Math.sin( state.theta * gain ) * this.radiusSafe,
+ Math.cos( state.theta * gain ) * this.radiusSafe,
+ 0
+ )
+ }
+ this.phiLine.setGeometry( this.phiGeometry )
+ }
+ if( state.phi !== this.target.phi ){
+
+ this.phiMesh.rotation.y = state.phi - Math.PI / 2
+ }
+ if( typeof this.onValueChange === 'function' ) this.onValueChange.call( this )
+ }
+ }
+})
+
+
+
+module.exports = {BlochSphere}
+
+
+
diff --git a/packages/quantum-js-vis/Q-Circuit-Editor.css b/packages/quantum-js-vis/Q-Circuit-Editor.css
new file mode 100644
index 0000000..6a6a261
--- /dev/null
+++ b/packages/quantum-js-vis/Q-Circuit-Editor.css
@@ -0,0 +1,900 @@
+/*
+
+ Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+
+*/
+@charset "utf-8";
+
+
+
+
+
+
+
+
+
+/*
+
+ Z indices:
+
+ Clipboard =100
+ Selected op 10
+ Operation 0
+ Shadow -10
+ Background -20
+
+
+
+
+
+ Circuit
+
+ Menu Moments
+ ╭───────┬───┬───┬───┬───╮
+ │ ≡ ↘ │ 1 │ 2 │ 3 │ + │ Add moment
+ ├───┬───┼───┼───┼───┼───╯
+ R │ 0 │|0⟩│ H │ C0│ X │ -
+ e ├───┼───┼───┼───┼───┤
+ g │ 1 │|0⟩│ I │ C1│ X │ -
+ s ├───┼───┴───┴───┴───┘
+ │ + │ - - - -
+ ╰───╯
+ Add
+ register
+
+
+ Circuit Palette
+
+ ╭───────────────────┬───╮
+ │ H X Y Z S T π M … │ @ │
+ ╰───────────────────┴───╯
+
+
+ Circuit clipboard
+
+ ┌───────────────┐
+ ▟│ ┌───┬───────┐ │
+ █│ │ H │ X#0.0 │ │
+ █│ ├───┼───────┤ │
+ █│ │ I │ X#0.1 │ │
+ █│ └───┴───────┘ │
+ █└───────────────┘
+ ███████████████▛
+
+
+
+ ◢◣
+ ◢■■■■◣
+◢■■■■■■■■◣
+◥■■■■■■■■◤
+ ◥■■■■◤
+ ◥◤
+
+
+ ◢■■■■■■◤
+ ◢◤ ◢◤
+◢■■■■■■◤
+
+
+ ───────────
+ ╲ ╱ ╱ ╱
+ ╳ ╱ ╱
+ ╱ ╲╱ ╱
+ ───────
+
+
+ ─────⦢
+ ╱ ╱
+⦣─────
+
+
+*/
+
+
+
+
+
+.Q-circuit,
+.Q-circuit-palette {
+
+ position: relative;
+ width: 100%;
+}
+.Q-circuit-palette {
+
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ line-height: 0;
+}
+.Q-circuit-palette > div {
+
+ display: inline-block;
+ position: relative;
+ width: 4rem;
+ height: 4rem;
+}
+
+
+.Q-circuit {
+
+ margin: 1rem 0 2rem 0;
+ /*border-top: 2px solid hsl( 0, 0%, 50% );*/
+}
+.Q-parameters-box,
+.Q-circuit-board-foreground {
+ line-height: 3.85rem;
+ width: auto;
+}
+
+
+
+
+
+
+ /***************/
+ /* */
+ /* Toolbar */
+ /* */
+/***************/
+
+
+.Q-circuit-toolbar {
+
+ position: relative;
+ display: block;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ margin-bottom: 0.5rem;
+
+ box-sizing: border-box;
+ display: grid;
+ grid-auto-columns: 3.6rem;
+ grid-auto-rows: 3.0rem;
+ grid-auto-flow: column;
+
+}
+.Q-circuit-button {
+
+ position: relative;
+ display: inline-block;
+ /*margin: 0 0.5rem 0.5rem 0;*/
+ width: 3.6rem;
+ height: 3rem;
+/* box-shadow:
+ -0.1rem -0.1rem 0 rgba( 255, 255, 255, 0.8 ),
+ 0.1rem 0.1rem 0.1rem rgba( 0, 0, 0, 0.35 );*/
+
+ border-top: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 100%
+ );
+ border-right: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 90%
+ );
+ border-bottom: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 85%
+ );
+ border-left: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 97%
+ );
+ background: var( --Q-color-background );
+/* background:
+ var( --Q-color-background )
+ linear-gradient(
+
+ 0.4turn,
+
+ rgba( 0, 0, 0, 0.02 ),
+ rgba( 255, 255, 255, 0.1 )
+ );*/
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 30%
+ );
+ text-shadow: 1px 1px 0 rgba( 255, 255, 255, 1 );
+ /*border-radius: 0.5rem;*/
+ /*border-radius: 100%;*/
+ line-height: 2.9rem;
+ text-align: center;
+ cursor: pointer;
+ overflow: hidden;
+ font-weight: 900;
+}
+.Q-circuit-toolbar .Q-circuit-button:first-child {
+
+ border-top-left-radius: 0.5rem;
+ border-bottom-left-radius: 0.5rem;
+}
+.Q-circuit-toolbar .Q-circuit-button:last-child {
+
+ border-top-right-radius: 0.5rem;
+ border-bottom-right-radius: 0.5rem;
+}
+.Q-circuit-locked .Q-circuit-button,
+.Q-circuit-button[Q-disabled] {
+
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 85%
+ );
+ cursor: not-allowed;
+}
+.Q-circuit-locked .Q-circuit-toggle-lock {
+
+ color: inherit;
+ cursor: pointer;
+}
+
+
+
+
+.Q-circuit-board-container {
+
+ position: relative;
+ margin: 0 0 2rem 0;
+ margin: 0;
+ width: 100%;
+ max-height: 60vh;
+ overflow: scroll;
+}
+.Q-circuit-board {
+
+ position: relative;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+/*.Q-circuit-palette,*/
+.Q-circuit-board-foreground,
+.Q-circuit-board-background,
+.Q-circuit-clipboard {
+
+ box-sizing: border-box;
+ display: grid;
+ grid-auto-rows: 4rem;
+ grid-auto-columns: 4rem;
+ grid-auto-flow: column;
+}
+
+.Q-parameters-box {
+
+ position: absolute;
+ display: none;
+ z-index: 100;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: whitesmoke;
+}
+
+/*.Q-circuit-palette,*/
+.Q-circuit-board-foreground,
+.Q-circuit-board-background {
+
+ position: relative;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.Q-circuit-clipboard {
+
+ position: absolute;
+ z-index: 100;
+ min-width: 4rem;
+ min-height: 4rem;
+ transform: scale( 1.05 );
+}
+.Q-circuit-clipboard, .Q-circuit-clipboard > div {
+
+ cursor: grabbing;
+}
+.Q-circuit-clipboard-danger .Q-circuit-operation {
+
+ background-color: var( --Q-color-yellow );
+}
+.Q-circuit-clipboard-destroy {
+
+ animation-name: Q-circuit-clipboard-poof;
+ animation-fill-mode: forwards;
+ animation-duration: 0.3s;
+ animation-iteration-count: 1;
+}
+@keyframes Q-circuit-clipboard-poof {
+
+ 100% {
+
+ transform: scale( 1.5 );
+ opacity: 0;
+ }
+}
+.Q-circuit-board-background {
+
+ /*
+
+ Clipboard: 100
+ Operation: 0
+ Shadow: -10
+ Background: -20
+
+ */
+ position: absolute;
+ z-index: -20;
+ color: rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-board-background > div {
+
+/* transition:
+ background-color 0.2s,
+ color 0.2s;*/
+}
+.Q-circuit-board-background .Q-circuit-cell-highlighted {
+
+ background-color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+ /*transition: none;*/
+}
+
+
+
+
+.Q-circuit-register-wire {
+
+ position: absolute;
+ top: calc( 50% - 0.5px );
+ width: 100%;
+ height: 1px;
+ background-color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 50%
+ );
+}
+
+.Q-parameter-box-exit {
+ position: relative;
+ right: 0;
+ left: 0;
+ width: 5rem;
+ height: 2.5rem;
+ background-color: whitesmoke;
+}
+
+.Q-parameters-box > div,
+.Q-circuit-palette > div,
+.Q-circuit-clipboard > div,
+.Q-circuit-board-foreground > div {
+
+ text-align: center;
+}
+
+
+
+
+
+
+ /***************/
+ /* */
+ /* Headers */
+ /* */
+/***************/
+
+
+.Q-circuit-header {
+
+ position: sticky;
+ z-index: 2;
+ margin: 0;
+ /*background-color: var( --Q-color-background );*/
+ background-color: white;
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 75%
+ );
+ font-family: var( --Q-font-family-mono );
+}
+.Q-circuit-input.Q-circuit-cell-highlighted,
+.Q-circuit-header.Q-circuit-cell-highlighted {
+
+ background-color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+ color: black;
+}
+.Q-circuit-selectall {
+
+ z-index: 3;
+ margin: 0;
+ top: 0;
+ /*left: 4rem;*/
+ /*grid-column: 2;*/
+ left: 0;
+ grid-column-start: 1;
+ grid-column-end: 3;
+ grid-row: 1;
+ cursor: se-resize;
+}
+.Q-circuit-moment-label,
+.Q-circuit-moment-add {
+
+ grid-row: 1;
+ top: 0;
+ cursor: s-resize;
+}
+.Q-circuit-register-label,
+.Q-circuit-register-add {
+
+ grid-column: 2;
+ left: 4rem;
+ cursor: e-resize;
+}
+.Q-circuit-moment-add,
+.Q-circuit-register-add {
+
+ cursor: pointer;
+}
+.Q-circuit-moment-add,
+.Q-circuit-register-add {
+
+ display: none;
+}
+.Q-circuit-selectall,
+.Q-circuit-moment-label,
+.Q-circuit-moment-add {
+
+ border-bottom: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+}
+.Q-circuit-selectall,
+.Q-circuit-register-label,
+.Q-circuit-register-add {
+
+ border-right: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 95%
+ );
+}
+.Q-circuit-input {
+
+ position: sticky;
+ z-index: 2;
+ grid-column: 1;
+ left: 0;
+ /*background-color: var( --Q-color-background );*/
+ background-color: white;
+ font-size: 1.5rem;
+ font-weight: 900;
+ font-family: var( --Q-font-family-mono );
+}
+
+
+
+
+
+
+.Q-circuit-operation-link-container {
+
+ --Q-link-stroke: 3px;
+ --Q-link-radius: 100%;
+
+ display: block;
+ position: relative;
+ left: calc( 50% - ( var( --Q-link-stroke ) / 2 ));
+ width: 50%;
+ height: 100%;
+ overflow: hidden;
+}
+.Q-circuit-operation-link-container.Q-circuit-cell-highlighted {
+
+ background-color: transparent;
+}
+.Q-circuit-operation-link {
+
+ display: block;
+ position: absolute;
+ width: calc( var( --Q-link-stroke ) * 2 );
+ height: calc( 100% - 4rem + var( --Q-link-stroke ));
+ /*border: var( --Q-link-stroke ) solid hsl( 0, 0%, 50% );*/
+ border: var( --Q-link-stroke ) solid hsl(
+
+ var( --Q-color-background-hue ),
+ 10%,
+ 30%
+ );
+
+ /*border: var( --Q-link-stroke ) solid var( --Q-color-orange );*/
+
+ transform: translate( -50%, calc( 2rem - ( var( --Q-link-stroke ) / 2 )));
+ transform-origin: center;
+}
+.Q-circuit-operation-link.Q-circuit-operation-link-curved {
+
+ width: calc( var( --Q-link-radius ) - var( --Q-link-stroke ));
+ width: 200%;
+ border-radius: 100%;
+}
+
+
+
+
+
+
+ /******************/
+ /* */
+ /* Operations */
+ /* */
+/******************/
+
+.Q-circuit-operation {
+
+ position: relative;
+ /*--Q-operation-color-hue: var( --Q-color-green-hue );
+ --Q-operation-color-main: var( --Q-color-green );*/
+
+ --Q-operation-color-hue: var( --Q-color-blue-hue );
+ --Q-operation-color-main: hsl(
+
+ var( --Q-operation-color-hue ),
+ 10%,
+ 35%
+ );
+
+ --Q-operation-color-light: hsl(
+
+ var( --Q-operation-color-hue ),
+ 10%,
+ 50%
+ );
+ --Q-operation-color-dark: hsl(
+
+ var( --Q-operation-color-hue ),
+ 10%,
+ 25%
+ );
+ color: white;
+ text-shadow: -0.05rem -0.05rem 0 rgba( 0, 0, 0, 0.1 );
+ font-size: 1.5rem;
+ line-height: 2.9rem;
+ font-weight: 900;
+ cursor: grab;
+}
+.Q-circuit-locked .Q-circuit-operation {
+
+ cursor: not-allowed;
+}
+.Q-circuit-operation-tile {
+
+ position: absolute;
+ top: 0.5rem;
+ left: 0.5rem;
+ right: 0.5rem;
+ bottom: 0.5rem;
+
+ /*margin: 0.5rem;*/
+ /*padding: 0.5rem;*/
+
+ /*box-shadow: 0.1rem 0.1rem 0.2rem rgba( 0, 0, 0, 0.2 );*/
+ border-radius: 0.2rem;
+ /*
+ border-top: 0.1rem solid var( --Q-operation-color-light );
+ border-left: 0.1rem solid var( --Q-operation-color-light );
+ border-right: 0.1rem solid var( --Q-operation-color-dark );
+ border-bottom: 0.1rem solid var( --Q-operation-color-dark );
+ */
+ background:
+ var( --Q-operation-color-main )
+ /*linear-gradient(
+
+ 0.45turn,
+ rgba( 255, 255, 255, 0.1 ),
+ rgba( 0, 0, 0, 0.05 )
+ )*/;
+}
+.Q-parameter-box-exit .Q-circuit-palette .Q-circuit-operation:hover {
+
+ /*background-color: rgba( 255, 255, 255, 0.6 );*/
+ background-color: white;
+}
+.Q-circuit-palette .Q-circuit-operation-tile {
+
+ --Q-before-rotation: 12deg;
+ --Q-before-x: 1px;
+ --Q-before-y: -2px;
+
+ --Q-after-rotation: -7deg;
+ --Q-after-x: -2px;
+ --Q-after-y: 3px;
+
+ box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-palette .Q-circuit-operation-tile:before,
+.Q-circuit-palette .Q-circuit-operation-tile:after {
+
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ border-radius: 0.2rem;
+ /*background-color: hsl( 0, 0%, 60% );*/
+
+ background-color: var( --Q-operation-color-dark );
+ transform:
+ translate( var( --Q-before-x ), var( --Q-before-y ))
+ rotate( var( --Q-before-rotation ));
+ z-index: -10;
+ /*z-index: 10;*/
+ display: block;
+ box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-palette .Q-circuit-operation-tile:after {
+
+ transform:
+ translate( var( --Q-after-x ), var( --Q-after-y ))
+ rotate( var( --Q-after-rotation ));
+ box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 );
+}
+.Q-circuit-operation:hover .Q-circuit-operation-tile {
+
+ color: white;
+}
+
+
+
+
+.Q-circuit-operation-hadamard .Q-circuit-operation-tile {
+
+ /*--Q-operation-color-hue: var( --Q-color-red-hue );*/
+ /*--Q-operation-color-main: var( --Q-color-red );*/
+
+ /*--Q-operation-color-hue: 0;
+ --Q-operation-color-main: hsl( 0, 0%, 10% );*/
+
+
+/* background:
+ linear-gradient(
+
+ -33deg,
+ var( --Q-color-blue ) 20%,
+ #6f3c69 50%,
+ var( --Q-color-red ) 80%
+ );*/
+}
+.Q-circuit-operation-identity .Q-circuit-operation-tile,
+.Q-circuit-operation-control .Q-circuit-operation-tile,
+.Q-circuit-operation-target .Q-circuit-operation-tile {
+
+ /*--Q-operation-color-hue: var( --Q-color-orange-hue );*/
+ /*--Q-operation-color-main: var( --Q-color-orange );*/
+ border-radius: 100%;
+}
+.Q-circuit-operation-identity .Q-circuit-operation-tile,
+.Q-circuit-operation-control .Q-circuit-operation-tile {
+
+ top: calc( 50% - 0.7rem );
+ left: calc( 50% - 0.7rem );
+ width: 1.4rem;
+ height: 1.4rem;
+ overflow: hidden;
+/* --Q-operation-color-hue: 0;
+ --Q-operation-color-main: hsl( 0, 0%, 10% );*/
+}
+.Q-circuit-operation-pauli-x,
+.Q-circuit-operation-pauli-y,
+.Q-circuit-operation-pauli-z {
+
+ /*--Q-operation-color-hue: var( --Q-color-red-hue );*/
+ /*--Q-operation-color-main: var( --Q-color-red );*/
+
+/* --Q-operation-color-hue: 0;
+ --Q-operation-color-main: hsl( 0, 0%, 30% );*/
+}
+.Q-circuit-operation-swap .Q-circuit-operation-tile {
+
+ top: calc( 50% - 0.55rem );
+ left: calc( 50% - 0.55rem );
+ width: 1.2rem;
+ height: 1.2rem;
+ border-radius: 0;
+ transform-origin: center;
+ transform: rotate( 45deg );
+ font-size: 0;
+}
+
+.Q-parameter-box-input-container {
+ position: relative;
+ text-align: center;
+ grid-auto-columns: 4rem;
+ grid-auto-flow: column;
+}
+
+.Q-parameter-box-input {
+ position: relative;
+ border-radius: .2rem;
+ margin-left: 10px;
+ font-family: var( --Q-font-family-mono );
+}
+
+.Q-parameter-input-label {
+ position: relative;
+ color: var( --Q-color-blue );
+ font-family: var( --Q-font-family-mono );
+}
+
+
+
+
+ /********************/
+ /* */
+ /* Other states */
+ /* */
+/********************/
+
+
+.Q-circuit-palette > div:hover,
+.Q-circuit-board-foreground > div:hover {
+
+ outline: 2px solid var( --Q-hyperlink-internal-color );
+ outline-offset: -2px;
+}
+.Q-circuit-palette > div:hover .Q-circuit-operation-tile {
+
+ box-shadow: none;
+}
+/*.Q-circuit-palette > div:hover,*/
+.Q-circuit-board-foreground > div:hover {
+
+ background-color: white;
+ color: black;
+}
+
+
+
+
+
+
+.Q-circuit-clipboard > div,
+.Q-circuit-cell-selected {
+
+ background-color: white;
+}
+.Q-circuit-clipboard > div:before,
+.Q-circuit-cell-selected:before {
+
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ display: block;
+ z-index: -10;
+ box-shadow:
+ 0 0 1rem rgba( 0, 0, 0, 0.2 ),
+ 0.4rem 0.4rem 0.2rem rgba( 0, 0, 0, 0.2 );
+ outline: 1px solid hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 50%
+ );
+ /*outline-offset: -1px;*/
+}
+
+
+
+
+.Q-circuit-clipboard > div {
+
+ background-color: white;
+}
+.Q-circuit-clipboard > div:before {
+
+ /*
+
+ This was very helpful!
+ https://blog.dudak.me/2014/css-shadows-under-adjacent-elements/
+
+ */
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: -10;
+ display: block;
+ box-shadow: 0.4rem 0.4rem 0.3rem rgba( 0, 0, 0, 0.2 );
+}
+
+
+
+
+
+
+
+ /***************/
+ /* */
+ /* Buttons */
+ /* */
+/***************/
+
+
+.Q-circuit-locked .Q-circuit-toggle-lock,
+.Q-circuit-locked .Q-circuit-toggle-lock:hover {
+
+ background-color: var( --Q-color-red );
+}
+.Q-circuit-toggle-lock {
+
+ z-index: 3;
+ left: 0;
+ top: 0;
+ grid-column: 1;
+ grid-row: 1;
+ cursor: pointer;
+ font-size: 1.1rem;
+ text-shadow: none;
+ font-weight: normal;
+}
+.Q-circuit-button-undo,
+.Q-circuit-button-redo {
+
+ font-size: 1.2rem;
+ line-height: 2.6rem;
+ font-weight: normal;
+}
+
+
+
+.Q-circuit p {
+
+ padding: 1rem;
+ color: hsl(
+
+ var( --Q-color-background-hue ),
+ var( --Q-color-background-saturation ),
+ 66%
+ );
+}
+
+
+
diff --git a/packages/quantum-js-vis/Q-Circuit-Editor.js b/packages/quantum-js-vis/Q-Circuit-Editor.js
new file mode 100644
index 0000000..7c225a6
--- /dev/null
+++ b/packages/quantum-js-vis/Q-Circuit-Editor.js
@@ -0,0 +1,2281 @@
+// Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+const {Q, Circuit, Gate, logger, misc, mathf } = require('quantum-js-util');
+Editor = function( circuit, targetEl ){
+ // First order of business,
+ // we require a valid circuit.
+
+ if( circuit instanceof Circuit !== true ) circuit = new Circuit()
+ this.circuit = circuit
+ this.index = Editor.index ++
+
+
+ // Editor is all about the DOM
+ // so we’re going to get some use out of this
+ // stupid (but convenient) shorthand here.
+
+ const createDiv = function(){
+
+ return document.createElement( 'div' )
+ }
+
+
+
+
+
+
+ // We want to “name” our circuit editor instance
+ // but more importantly we want to give it a unique DOM ID.
+ // Keep in mind we can have MULTIPLE editors
+ // for the SAME circuit!
+ // This is a verbose way to do it,
+ // but each step is clear and I needed clarity today! ;)
+
+ this.name = typeof circuit.name === 'string' ?
+ circuit.name :
+ 'Q Editor '+ this.index
+
+
+ // If we’ve been passed a target DOM element
+ // we should use that as our circuit element.
+
+ if( typeof targetEl === 'string' ) targetEl = document.getElementById( targetEl )
+ const circuitEl = targetEl instanceof HTMLElement ? targetEl : createDiv()
+ circuitEl.classList.add( 'Q-circuit' )
+
+
+ // If the target element already has an ID
+ // then we want to use that as our domID.
+
+ if( typeof circuitEl.getAttribute( 'id' ) === 'string' ){
+
+ this.domId = circuitEl.getAttribute( 'id' )
+ }
+
+
+ // Otherwise let’s transform our name value
+ // into a usable domId.
+
+ else {
+
+ let domIdBase = this.name
+ .replace( /^[^a-z]+|[^\w:.-]+/gi, '-' ),
+ domId = domIdBase,
+ domIdAttempt = 1
+
+ while( document.getElementById( domId ) !== null ){
+
+ domIdAttempt ++
+ domId = domIdBase +'-'+ domIdAttempt
+ }
+ this.domId = domId
+ circuitEl.setAttribute( 'id', this.domId )
+ }
+
+
+
+
+ // We want a way to easily get to the circuit
+ // from this interface’s DOM element.
+ // (But we don’t need a way to reference this DOM element
+ // from the circuit. A circuit can have many DOM elements!)
+ // And we also want an easy way to reference this DOM element
+ // from this Editor instance.
+
+ circuitEl.circuit = circuit
+ this.domElement = circuitEl
+
+
+ // Create a toolbar for containing buttons.
+
+ const toolbarEl = createDiv()
+ circuitEl.appendChild( toolbarEl )
+ toolbarEl.classList.add( 'Q-circuit-toolbar' )
+
+
+ // Create a toggle switch for locking the circuit.
+
+ const lockToggle = createDiv()
+ toolbarEl.appendChild( lockToggle )
+ lockToggle.classList.add( 'Q-circuit-button', 'Q-circuit-toggle', 'Q-circuit-toggle-lock' )
+ lockToggle.setAttribute( 'title', 'Lock / unlock' )
+ lockToggle.innerText = '🔓'
+
+
+ // Create an “Undo” button
+ // that enables and disables
+ // based on available undo history.
+
+ const undoButton = createDiv()
+ toolbarEl.appendChild( undoButton )
+ undoButton.classList.add( 'Q-circuit-button', 'Q-circuit-button-undo' )
+ undoButton.setAttribute( 'title', 'Undo' )
+ undoButton.setAttribute( 'Q-disabled', 'Q-disabled' )
+ undoButton.innerHTML = '⟲'
+ window.addEventListener( 'History undo is depleted', function( event ){
+
+ if( event.detail.instance === circuit )
+ undoButton.setAttribute( 'Q-disabled', 'Q-disabled' )
+ })
+ window.addEventListener( 'History undo is capable', function( event ){
+
+ if( event.detail.instance === circuit )
+ undoButton.removeAttribute( 'Q-disabled' )
+ })
+
+
+ // Create an “Redo” button
+ // that enables and disables
+ // based on available redo history.
+
+ const redoButton = createDiv()
+ toolbarEl.appendChild( redoButton )
+ redoButton.classList.add( 'Q-circuit-button', 'Q-circuit-button-redo' )
+ redoButton.setAttribute( 'title', 'Redo' )
+ redoButton.setAttribute( 'Q-disabled', 'Q-disabled' )
+ redoButton.innerHTML = '⟳'
+ window.addEventListener( 'History redo is depleted', function( event ){
+
+ if( event.detail.instance === circuit )
+ redoButton.setAttribute( 'Q-disabled', 'Q-disabled' )
+ })
+ window.addEventListener( 'History redo is capable', function( event ){
+
+ if( event.detail.instance === circuit )
+ redoButton.removeAttribute( 'Q-disabled' )
+ })
+
+
+ // Create a button for joining
+ // an “identity cursor”
+ // and one or more same-gate operations
+ // into a controlled operation.
+ // (Will be enabled / disabled from elsewhere.)
+
+ const controlButton = createDiv()
+ toolbarEl.appendChild( controlButton )
+ controlButton.classList.add( 'Q-circuit-button', 'Q-circuit-toggle', 'Q-circuit-toggle-control' )
+ controlButton.setAttribute( 'title', 'Create controlled operation' )
+ controlButton.setAttribute( 'Q-disabled', 'Q-disabled' )
+ controlButton.innerText = 'C'
+
+
+ // Create a button for joining
+ // two “identity cursors”
+ // into a swap operation.
+ // (Will be enabled / disabled from elsewhere.)
+
+ const swapButton = createDiv()
+ toolbarEl.appendChild( swapButton )
+ swapButton.classList.add( 'Q-circuit-button', 'Q-circuit-toggle-swap' )
+ swapButton.setAttribute( 'title', 'Create swap operation' )
+ swapButton.setAttribute( 'Q-disabled', 'Q-disabled' )
+ swapButton.innerText = 'S'
+
+
+ // Create a circuit board container
+ // so we can house a scrollable circuit board.
+
+ const boardContainerEl = createDiv()
+ circuitEl.appendChild( boardContainerEl )
+ boardContainerEl.classList.add( 'Q-circuit-board-container' )
+ //boardContainerEl.addEventListener( 'touchstart', Editor.onPointerPress )
+ boardContainerEl.addEventListener( 'mouseleave', function(){
+ Editor.unhighlightAll( circuitEl )
+ })
+
+ const boardEl = createDiv()
+ boardContainerEl.appendChild( boardEl )
+ boardEl.classList.add( 'Q-circuit-board' )
+
+ const backgroundEl = createDiv()
+ boardEl.appendChild( backgroundEl )
+ backgroundEl.classList.add( 'Q-circuit-board-background' )
+
+ const parameterEl = createDiv()
+ boardEl.appendChild( parameterEl )
+ parameterEl.classList.add( 'Q-parameters-box' )
+ // Create background highlight bars
+ // for each row.
+
+ for( let i = 0; i < circuit.bandwidth; i ++ ){
+
+ const rowEl = createDiv()
+ backgroundEl.appendChild( rowEl )
+ rowEl.style.position = 'relative'
+ rowEl.style.gridRowStart = i + 2
+ rowEl.style.gridColumnStart = 1
+ rowEl.style.gridColumnEnd = Editor.momentIndexToGridColumn( circuit.timewidth ) + 1
+ rowEl.setAttribute( 'register-index', i + 1 )
+
+ const wireEl = createDiv()
+ rowEl.appendChild( wireEl )
+ wireEl.classList.add( 'Q-circuit-register-wire' )
+ }
+
+
+ // Create background highlight bars
+ // for each column.
+
+ for( let i = 0; i < circuit.timewidth; i ++ ){
+
+ const columnEl = createDiv()
+ backgroundEl.appendChild( columnEl )
+ columnEl.style.gridRowStart = 2
+ columnEl.style.gridRowEnd = Editor.registerIndexToGridRow( circuit.bandwidth ) + 1
+ columnEl.style.gridColumnStart = i + 3
+ columnEl.setAttribute( 'moment-index', i + 1 )
+ }
+
+
+ // Create the circuit board foreground
+ // for all interactive elements.
+
+ const foregroundEl = createDiv()
+ boardEl.appendChild( foregroundEl )
+ foregroundEl.classList.add( 'Q-circuit-board-foreground' )
+
+
+ // Add “Select All” toggle button to upper-left corner.
+
+ const selectallEl = createDiv()
+ foregroundEl.appendChild( selectallEl )
+ selectallEl.classList.add( 'Q-circuit-header', 'Q-circuit-selectall' )
+ selectallEl.setAttribute( 'title', 'Select all' )
+ selectallEl.setAttribute( 'moment-index', '0' )
+ selectallEl.setAttribute( 'register-index', '0' )
+ selectallEl.innerHTML = '↘'
+
+
+ // Add register index symbols to left-hand column.
+
+ for( let i = 0; i < circuit.bandwidth; i ++ ){
+
+ const
+ registerIndex = i + 1,
+ registersymbolEl = createDiv()
+
+ foregroundEl.appendChild( registersymbolEl )
+ registersymbolEl.classList.add( 'Q-circuit-header', 'Q-circuit-register-label' )
+ registersymbolEl.setAttribute( 'title', 'Register '+ registerIndex +' of '+ circuit.bandwidth )
+ registersymbolEl.setAttribute( 'register-index', registerIndex )
+ registersymbolEl.style.gridRowStart = Editor.registerIndexToGridRow( registerIndex )
+ registersymbolEl.innerText = registerIndex
+ }
+
+
+ // Add “Add register” button.q
+
+ const addRegisterEl = createDiv()
+ foregroundEl.appendChild( addRegisterEl )
+ addRegisterEl.classList.add( 'Q-circuit-header', 'Q-circuit-register-add' )
+ addRegisterEl.setAttribute( 'title', 'Add register' )
+ addRegisterEl.style.gridRowStart = Editor.registerIndexToGridRow( circuit.bandwidth + 1 )
+ addRegisterEl.innerText = '+'
+
+
+ // Add moment index symbols to top row.
+
+ for( let i = 0; i < circuit.timewidth; i ++ ){
+
+ const
+ momentIndex = i + 1,
+ momentsymbolEl = createDiv()
+
+ foregroundEl.appendChild( momentsymbolEl )
+ momentsymbolEl.classList.add( 'Q-circuit-header', 'Q-circuit-moment-label' )
+ momentsymbolEl.setAttribute( 'title', 'Moment '+ momentIndex +' of '+ circuit.timewidth )
+ momentsymbolEl.setAttribute( 'moment-index', momentIndex )
+ momentsymbolEl.style.gridColumnStart = Editor.momentIndexToGridColumn( momentIndex )
+ momentsymbolEl.innerText = momentIndex
+ }
+
+
+ // Add “Add moment” button.
+
+ const addMomentEl = createDiv()
+ foregroundEl.appendChild( addMomentEl )
+ addMomentEl.classList.add( 'Q-circuit-header', 'Q-circuit-moment-add' )
+ addMomentEl.setAttribute( 'title', 'Add moment' )
+ addMomentEl.style.gridColumnStart = Editor.momentIndexToGridColumn( circuit.timewidth + 1 )
+ addMomentEl.innerText = '+'
+
+
+ // Add input values.
+
+ circuit.qubits.forEach( function( qubit, i ){
+
+ const
+ rowIndex = i + 1,
+ inputEl = createDiv()
+
+ inputEl.classList.add( 'Q-circuit-header', 'Q-circuit-input' )
+ inputEl.setAttribute( 'title', `Qubit #${ rowIndex } starting value` )
+ inputEl.setAttribute( 'register-index', rowIndex )
+ inputEl.style.gridRowStart = Editor.registerIndexToGridRow( rowIndex )
+ inputEl.innerText = qubit.beta.toText()
+ foregroundEl.appendChild( inputEl )
+ })
+
+
+ // Add operations.
+
+ circuit.operations.forEach( function( operation ){
+ Editor.set( circuitEl, operation )
+ })
+
+
+ // Add event listeners.
+
+ circuitEl.addEventListener( 'mousedown', Editor.onPointerPress )
+ circuitEl.addEventListener( 'touchstart', Editor.onPointerPress )
+ window.addEventListener(
+
+ 'Circuit.set$',
+ Editor.prototype.onExternalSet.bind( this )
+ )
+ window.addEventListener(
+
+ 'Circuit.clear$',
+ Editor.prototype.onExternalClear.bind( this )
+ )
+
+
+ // How can we interact with this circuit
+ // through code? (How cool is this?!)
+
+ const referenceEl = document.createElement( 'p' )
+ circuitEl.appendChild( referenceEl )
+ referenceEl.innerHTML = `
+ This circuit is accessible in your
+ JavaScript console
+ as document.getElementById('${ this.domId }').circuit
`
+ //document.getElementById('Q-Editor-0').circuit
+ //$('#${ this.domId }')
+
+
+ // Put a note in the JavaScript console
+ // that includes how to reference the circuit via code
+ // and an ASCII diagram for reference.
+
+ logger.warn( 0.5,
+ `\n\nCreated a DOM interface for $('#${ this.domId }').circuit\n\n`,
+ circuit.toDiagram(),
+ '\n\n\n'
+ )
+}
+
+
+// Augment Circuit to have this functionality.
+
+Circuit.toDom = function( circuit, targetEl ){
+
+ return new Editor( circuit, targetEl ).domElement
+}
+Circuit.prototype.toDom = function( targetEl ){
+
+ return new Editor( this, targetEl ).domElement
+}
+
+
+
+
+
+
+
+
+Object.assign( Editor, {
+
+ index: 0,
+ help: function(){ return logger.help( this )},
+ dragEl: null,
+ gridColumnToMomentIndex: function( gridColumn ){ return +gridColumn - 2 },
+ momentIndexToGridColumn: function( momentIndex ){ return momentIndex + 2 },
+ gridRowToRegisterIndex: function( gridRow ){ return +gridRow - 1 },
+ registerIndexToGridRow: function( registerIndex ){ return registerIndex + 1 },
+ gridSize: 4,// CSS: grid-auto-columns = grid-auto-rows = 4rem.
+ pointToGrid: function( p ){
+
+
+ // Take a 1-dimensional point value
+ // (so either an X or a Y but not both)
+ // and return what CSS grid cell contains it
+ // based on our 4rem × 4rem grid setup.
+
+ const rem = parseFloat( getComputedStyle( document.documentElement ).fontSize )
+ return 1 + Math.floor( p / ( rem * Editor.gridSize ))
+ },
+ gridToPoint: function( g ){
+
+
+ // Take a 1-dimensional grid cell value
+ // (so either a row or a column but not both)
+ // and return the minimum point value it contains.
+
+ const rem = parseFloat( getComputedStyle( document.documentElement ).fontSize )
+ return rem * Editor.gridSize * ( g - 1 )
+ },
+ getInteractionCoordinates: function( event, pageOrClient ){
+
+ if( typeof pageOrClient !== 'string' ) pageOrClient = 'client'//page
+ if( event.changedTouches &&
+ event.changedTouches.length ) return {
+
+ x: event.changedTouches[ 0 ][ pageOrClient +'X' ],
+ y: event.changedTouches[ 0 ][ pageOrClient +'Y' ]
+ }
+ return {
+ x: event[ pageOrClient +'X' ],
+ y: event[ pageOrClient +'Y' ]
+ }
+ },
+ createNewElement :function(element_type, element_parent, element_css) {
+ element = document.createElement(element_type)
+ if(element_css) element.classList.add(element_css)
+ if(element_parent) element_parent.appendChild( element )
+ return element
+ },
+ createPalette: function( targetEl ){
+
+ if( typeof targetEl === 'string' ) targetEl = document.getElementById( targetEl )
+
+ const
+ paletteEl = targetEl instanceof HTMLElement ? targetEl : document.createElement( 'div' ),
+ randomRangeAndSign = function( min, max ){
+
+ const r = min + Math.random() * ( max - min )
+ return Math.floor( Math.random() * 2 ) ? r : -r
+ }
+
+ //ltnln: added missing Braket operations.
+ paletteEl.classList.add( 'Q-circuit-palette' );
+ 'H,X,Y,Z,P,Rx,Ry,Rz,U,V,V†,S*,S†,T,T†,00,01,10,√S,iS,XX,XY,YY,ZZ,*'
+ .split( ',' )
+ .forEach( function( symbol ){
+
+ const gate = Gate.findBySymbol( symbol )
+
+ const operationEl = document.createElement( 'div' )
+ paletteEl.appendChild( operationEl )
+ operationEl.classList.add( 'Q-circuit-operation' )
+ operationEl.classList.add( 'Q-circuit-operation-'+ gate.nameCss )
+ operationEl.setAttribute( 'gate-symbol', symbol )
+ operationEl.setAttribute( 'title', gate.name )
+
+ const tileEl = document.createElement( 'div' )
+ operationEl.appendChild( tileEl )
+ tileEl.classList.add( 'Q-circuit-operation-tile' )
+ if( symbol !== Gate.CURSOR.symbol ) tileEl.innerText = symbol
+
+ ;[ 'before', 'after' ].forEach( function( layer ){
+
+ tileEl.style.setProperty( '--Q-'+ layer +'-rotation', randomRangeAndSign( 0.5, 4 ) +'deg' )
+ tileEl.style.setProperty( '--Q-'+ layer +'-x', randomRangeAndSign( 1, 4 ) +'px' )
+ tileEl.style.setProperty( '--Q-'+ layer +'-y', randomRangeAndSign( 1, 3 ) +'px' )
+ })
+ })
+
+ paletteEl.addEventListener( 'mousedown', Editor.onPointerPress )
+ paletteEl.addEventListener( 'touchstart', Editor.onPointerPress )
+ return paletteEl
+ },
+ toDom: function( circuit, targetEl ){
+
+ return new Editor( circuit, targetEl ).domElement
+ }
+})
+
+
+
+
+
+
+ /////////////////////////
+ // //
+ // Operation CLEAR //
+ // //
+/////////////////////////
+
+
+Editor.prototype.onExternalClear = function( event ){
+
+ if( event.detail.circuit === this.circuit ){
+
+ Editor.clear( this.domElement, {
+
+ momentIndex: event.detail.momentIndex,
+ registerIndices: event.detail.registerIndices
+ })
+ }
+}
+Editor.clear = function( circuitEl, operation ){
+
+ const momentIndex = operation.momentIndex
+ operation.registerIndices.forEach( function( registerIndex ){
+
+ Array
+ .from( circuitEl.querySelectorAll(
+
+ `[moment-index="${ momentIndex }"]`+
+ `[register-index="${ registerIndex }"]`
+
+ ))
+ .forEach( function( op ){
+
+ op.parentNode.removeChild( op )
+ })
+ })
+}
+
+
+
+
+
+
+ ///////////////////////
+ // //
+ // Operation SET //
+ // //
+///////////////////////
+
+
+Editor.prototype.onExternalSet = function( event ){
+
+ if( event.detail.circuit === this.circuit ){
+
+ Editor.set( this.domElement, event.detail.operation )
+ }
+}
+Editor.set = function( circuitEl, operation ){
+ const
+ backgroundEl = circuitEl.querySelector( '.Q-circuit-board-background' ),
+ foregroundEl = circuitEl.querySelector( '.Q-circuit-board-foreground' ),
+ circuit = circuitEl.circuit,
+ operationIndex = circuitEl.circuit.operations.indexOf( operation )
+
+ operation.registerIndices.forEach( function( registerIndex, i ){
+ const operationEl = document.createElement( 'div' )
+ foregroundEl.appendChild( operationEl )
+ operationEl.classList.add( 'Q-circuit-operation', 'Q-circuit-operation-'+ operation.gate.nameCss )
+ // operationEl.setAttribute( 'operation-index', operationIndex )
+ operationEl.setAttribute( 'gate-symbol', operation.gate.symbol )
+ operationEl.setAttribute( 'gate-index', operation.gate.index )// Used as an application-wide unique ID!
+ operationEl.setAttribute( 'moment-index', operation.momentIndex )
+ operationEl.setAttribute( 'register-index', registerIndex )
+ operationEl.setAttribute( 'register-array-index', i )// Where within the registerIndices array is this operations fragment located?
+ operationEl.setAttribute( 'is-controlled', operation.isControlled )
+ operationEl.setAttribute( 'title', operation.gate.name )
+ operationEl.style.gridColumnStart = Editor.momentIndexToGridColumn( operation.momentIndex )
+ operationEl.style.gridRowStart = Editor.registerIndexToGridRow( registerIndex )
+ if( operation.gate.has_parameters ) Object.keys(operation.gate.parameters).forEach( element => {
+ operationEl.setAttribute( element, operation.gate.parameters[element] ) //adds a parameter attribute to the operation!
+ })
+ const tileEl = document.createElement( 'div' )
+ operationEl.appendChild( tileEl )
+ tileEl.classList.add( 'Q-circuit-operation-tile' )
+ if( operation.gate.symbol !== Gate.CURSOR.symbol ) tileEl.innerText = operation.gate.symbol
+
+
+ // Add operation link wires
+ // for multi-qubit operations.
+
+ if( operation.registerIndices.length > 1 ){
+
+ operationEl.setAttribute( 'register-indices', operation.registerIndices )
+ operationEl.setAttribute( 'register-indices-index', i )
+ operationEl.setAttribute(
+
+ 'sibling-indices',
+ operation.registerIndices
+ .filter( function( siblingRegisterIndex ){
+
+ return registerIndex !== siblingRegisterIndex
+ })
+ )
+ operation.registerIndices.forEach( function( registerIndex, i ){
+
+ if( i < operation.registerIndices.length - 1 ){
+
+ const
+ siblingRegisterIndex = operation.registerIndices[ i + 1 ],
+ registerDelta = Math.abs( siblingRegisterIndex - registerIndex ),
+ start = Math.min( registerIndex, siblingRegisterIndex ),
+ end = Math.max( registerIndex, siblingRegisterIndex ),
+ containerEl = document.createElement( 'div' ),
+ linkEl = document.createElement( 'div' )
+
+ backgroundEl.appendChild( containerEl )
+ containerEl.setAttribute( 'moment-index', operation.momentIndex )
+ containerEl.setAttribute( 'register-index', registerIndex )
+ containerEl.classList.add( 'Q-circuit-operation-link-container' )
+ containerEl.style.gridRowStart = Editor.registerIndexToGridRow( start )
+ containerEl.style.gridRowEnd = Editor.registerIndexToGridRow( end + 1 )
+ containerEl.style.gridColumn = Editor.momentIndexToGridColumn( operation.momentIndex )
+
+ containerEl.appendChild( linkEl )
+ linkEl.classList.add( 'Q-circuit-operation-link' )
+ if( registerDelta > 1 ) linkEl.classList.add( 'Q-circuit-operation-link-curved' )
+ }
+ })
+ if( operation.isControlled && i === 0 ){
+ operationEl.classList.add( 'Q-circuit-operation-control' )
+ operationEl.setAttribute( 'title', 'Control' )
+ tileEl.innerText = ''
+ }
+ else operationEl.classList.add( 'Q-circuit-operation-target' )
+ }
+ })
+}
+
+
+
+
+Editor.isValidControlCandidate = function( circuitEl ){
+
+ const
+ selectedOperations = Array
+ .from( circuitEl.querySelectorAll( '.Q-circuit-cell-selected' ))
+
+
+ // We must have at least two operations selected,
+ // hopefully a control and something else,
+ // in order to attempt a join.
+
+ if( selectedOperations.length < 2 ) return false
+
+
+ // Note the different moment indices present
+ // among the selected operations.
+
+ const moments = selectedOperations.reduce( function( moments, operationEl ){
+
+ moments[ operationEl.getAttribute( 'moment-index' )] = true
+ return moments
+
+ }, {} )
+
+
+ // All selected operations must be in the same moment.
+
+ if( Object.keys( moments ).length > 1 ) return false
+
+
+ // If there are multi-register operations present,
+ // regardless of whether those are controls or swaps,
+ // all siblings must be present
+ // in order to join a new gate to this selection.
+
+ // I’m sure we can make this whole routine much more efficient
+ // but its results are correct and boy am I tired ;)
+
+ const allSiblingsPresent = selectedOperations
+ .reduce( function( status, operationEl ){
+
+ const registerIndicesString = operationEl.getAttribute( 'register-indices' )
+
+
+ // If it’s a single-register operation
+ // there’s no need to search further.
+
+ if( !registerIndicesString ) return status
+
+
+ // How many registers are in use
+ // by this operation?
+
+ const
+ registerIndicesLength = registerIndicesString
+ .split( ',' )
+ .map( function( registerIndex ){
+
+ return +registerIndex
+ })
+ .length,
+
+
+ // How many of this operation’s siblings
+ // (including itself) can we find?
+
+ allSiblingsLength = selectedOperations
+ .reduce( function( siblings, operationEl ){
+
+ if( operationEl.getAttribute( 'register-indices' ) === registerIndicesString ){
+
+ siblings.push( operationEl )
+ }
+ return siblings
+
+ }, [])
+ .length
+
+
+ // Did we find all of the siblings for this operation?
+ // Square that with previous searches.
+
+ return status && allSiblingsLength === registerIndicesLength
+
+ }, true )
+
+
+ // If we’re missing some siblings
+ // then we cannot modify whatever we have selected here.
+
+ if( allSiblingsPresent !== true ) return false
+
+ // Note the different gate types present
+ // among the selected operations.
+
+ const gates = selectedOperations.reduce( function( gates, operationEl ){
+ const gateSymbol = operationEl.getAttribute( 'gate-symbol' )
+ if( !mathf.isUsefulInteger( gates[ gateSymbol ])) gates[ gateSymbol ] = 1
+ else gates[ gateSymbol ] ++
+ return gates
+
+ }, {} )
+
+
+ // Note if each operation is already controlled or not.
+
+ const {
+
+ totalControlled,
+ totalNotControlled
+
+ } = selectedOperations
+ .reduce( function( stats, operationEl ){
+
+ if( operationEl.getAttribute( 'is-controlled' ) === 'true' )
+ stats.totalControlled ++
+ else stats.totalNotControlled ++
+ return stats
+
+ }, {
+
+ totalControlled: 0,
+ totalNotControlled: 0
+ })
+
+ // This could be ONE “identity cursor”
+ // and one or more of a regular single gate
+ // that is NOT already controlled.
+
+ if( gates[ Gate.CURSOR.symbol ] === 1 &&
+ Object.keys( gates ).length === 2 &&
+ totalNotControlled === selectedOperations.length ){
+
+ return true
+ }
+
+
+ // There’s NO “identity cursor”
+ // but there is one or more of specific gate type
+ // and at least one of those is already controlled.
+
+ if( gates[ Gate.CURSOR.symbol ] === undefined &&
+ Object.keys( gates ).length === 1 &&
+ totalControlled > 0 &&
+ totalNotControlled > 0 ){
+
+ return true
+ }
+
+
+ // Any other combination allowed? Nope!
+
+ return false
+}
+Editor.createControl = function( circuitEl ){
+
+ if( Editor.isValidControlCandidate( circuitEl ) !== true ) return this
+
+
+ const
+ circuit = circuitEl.circuit,
+ selectedOperations = Array
+ .from( circuitEl.querySelectorAll( '.Q-circuit-cell-selected' )),
+
+
+ // Are any of these controlled operations??
+ // If so, we need to find its control component
+ // and re-use it.
+
+ existingControlEl = selectedOperations.find( function( operationEl ){
+
+ return (
+
+ operationEl.getAttribute( 'is-controlled' ) === 'true' &&
+ operationEl.getAttribute( 'register-array-index' ) === '0'
+ )
+ }),
+
+
+ // One control. One or more targets.
+
+ control = existingControlEl || selectedOperations
+ .find( function( el ){
+
+ return el.getAttribute( 'gate-symbol' ) === Gate.CURSOR.symbol
+ }),
+ targets = selectedOperations
+ .reduce( function( targets, el ){
+
+ //if( el.getAttribute( 'gate-symbol' ) !== '!' ) targets.push( el )
+ if( el !== control ) targets.push( el )
+ return targets
+
+ }, [] )
+
+
+ // Ready to roll.
+
+ circuit.history.createEntry$()
+ selectedOperations.forEach( function( operationEl ){
+
+ circuit.clear$(
+
+ +operationEl.getAttribute( 'moment-index' ),
+ +operationEl.getAttribute( 'register-index' )
+ )
+ })
+ circuit.set$(
+ targets[ 0 ].getAttribute( 'gate-symbol' ),
+ +control.getAttribute( 'moment-index' ),
+ [ +control.getAttribute( 'register-index' )].concat(
+
+ targets.reduce( function( registers, operationEl ){
+
+ registers.push( +operationEl.getAttribute( 'register-index' ))
+ return registers
+
+ }, [] )
+ )
+ )
+
+
+ // Update our toolbar button states.
+
+ Editor.onSelectionChanged( circuitEl )
+ Editor.onCircuitChanged( circuitEl )
+
+ return this
+}
+
+
+
+
+Editor.isValidSwapCandidate = function( circuitEl ){
+
+ const
+ selectedOperations = Array
+ .from( circuitEl.querySelectorAll( '.Q-circuit-cell-selected' ))
+
+
+ // We can only swap between two registers.
+ // No crazy rotation-swap bullshit. (Yet.)
+ if( selectedOperations.length !== 2 ) return false
+
+
+ // Both operations must be “identity cursors.”
+ // If so, we are good to go.
+
+ areBothCursors = selectedOperations.every( function( operationEl ){
+
+ return operationEl.getAttribute( 'gate-symbol' ) === Gate.CURSOR.symbol
+ })
+ if( areBothCursors ) return true
+
+
+ // Otherwise this is not a valid swap candidate.
+
+ return false
+}
+Editor.createSwap = function( circuitEl ){
+
+ if( Editor.isValidSwapCandidate( circuitEl ) !== true ) return this
+
+ const
+ selectedOperations = Array
+ .from( circuitEl.querySelectorAll( '.Q-circuit-cell-selected' )),
+ momentIndex = +selectedOperations[ 0 ].getAttribute( 'moment-index' )
+ registerIndices = selectedOperations
+ .reduce( function( registerIndices, operationEl ){
+
+ registerIndices.push( +operationEl.getAttribute( 'register-index' ))
+ return registerIndices
+
+ }, [] ),
+ circuit = circuitEl.circuit
+
+
+ // Create the swap operation.
+
+ circuit.history.createEntry$()
+ selectedOperations.forEach( function( operation ){
+
+ circuit.clear$(
+
+ +operation.getAttribute( 'moment-index' ),
+ +operation.getAttribute( 'register-index' )
+ )
+ })
+ circuit.set$(
+
+ Gate.SWAP,
+ momentIndex,
+ registerIndices
+ )
+
+
+ // Update our toolbar button states.
+
+ Editor.onSelectionChanged( circuitEl )
+ Editor.onCircuitChanged( circuitEl )
+
+ return this
+}
+
+
+
+
+Editor.onSelectionChanged = function( circuitEl ){
+
+ const controlButtonEl = circuitEl.querySelector( '.Q-circuit-toggle-control' )
+ if( Editor.isValidControlCandidate( circuitEl )){
+
+ controlButtonEl.removeAttribute( 'Q-disabled' )
+ }
+ else controlButtonEl.setAttribute( 'Q-disabled', true )
+
+ const swapButtonEl = circuitEl.querySelector( '.Q-circuit-toggle-swap' )
+ if( Editor.isValidSwapCandidate( circuitEl )){
+
+ swapButtonEl.removeAttribute( 'Q-disabled' )
+ }
+ else swapButtonEl.setAttribute( 'Q-disabled', true )
+}
+Editor.onCircuitChanged = function( circuitEl ){
+
+ const circuit = circuitEl.circuit
+ window.dispatchEvent( new CustomEvent(
+
+ 'Q gui altered circuit',
+ { detail: { circuit: circuit }}
+ ))
+
+ // Should we trigger a circuit.evaluate$() here?
+ // Particularly when we move all that to a new thread??
+ // console.log( originCircuit.report$() ) ??
+}
+
+
+
+
+
+Editor.unhighlightAll = function( circuitEl ){
+
+ Array.from( circuitEl.querySelectorAll(
+
+ '.Q-circuit-board-background > div,'+
+ '.Q-circuit-board-foreground > div'
+ ))
+ .forEach( function( el ){
+
+ el.classList.remove( 'Q-circuit-cell-highlighted' )
+ })
+}
+
+
+
+
+
+
+ //////////////////////
+ // //
+ // Pointer MOVE //
+ // //
+//////////////////////
+
+
+Editor.onPointerMove = function( event ){
+
+
+ // We need our cursor coordinates straight away.
+ // We’ll use that both for dragging (immediately below)
+ // and for hover highlighting (further below).
+ // Let’s also hold on to a list of all DOM elements
+ // that contain this X, Y point
+ // and also see if one of those is a circuit board container.
+
+ const
+ { x, y } = Editor.getInteractionCoordinates( event ),
+ foundEls = document.elementsFromPoint( x, y ),
+ boardContainerEl = foundEls.find( function( el ){
+
+ return el.classList.contains( 'Q-circuit-board-container' )
+ })
+
+
+ // Are we in the middle of a circuit clipboard drag?
+ // If so we need to move that thing!
+
+ if( Editor.dragEl !== null ){
+
+
+ // ex. Don’t scroll on touch devices!
+
+ event.preventDefault()
+
+
+ // This was a very useful resource
+ // for a reality check on DOM coordinates:
+ // https://javascript.info/coordinates
+
+ Editor.dragEl.style.left = ( x + window.pageXOffset + Editor.dragEl.offsetX ) +'px'
+ Editor.dragEl.style.top = ( y + window.pageYOffset + Editor.dragEl.offsetY ) +'px'
+
+ if( !boardContainerEl && Editor.dragEl.circuitEl ) Editor.dragEl.classList.add( 'Q-circuit-clipboard-danger' )
+ else Editor.dragEl.classList.remove( 'Q-circuit-clipboard-danger' )
+ }
+
+
+ // If we’re not over a circuit board container
+ // then there’s no highlighting work to do
+ // so let’s bail now.
+
+ if( !boardContainerEl ) return
+
+
+ // Now we know we have a circuit board
+ // so we must have a circuit
+ // and if that’s locked then highlighting changes allowed!
+
+ const circuitEl = boardContainerEl.closest( '.Q-circuit' )
+ if( circuitEl.classList.contains( 'Q-circuit-locked' )) return
+
+
+ // Ok, we’ve found a circuit board.
+ // First, un-highlight everything.
+
+ Array.from( boardContainerEl.querySelectorAll(`
+
+ .Q-circuit-board-background > div,
+ .Q-circuit-board-foreground > div
+
+ `)).forEach( function( el ){
+
+ el.classList.remove( 'Q-circuit-cell-highlighted' )
+ })
+
+
+ // Let’s prioritize any element that is “sticky”
+ // which means it can appear OVER another grid cell.
+
+
+ const
+ cellEl = foundEls.find( function( el ){
+
+ const style = window.getComputedStyle( el )
+ return (
+
+ style.position === 'sticky' && (
+
+ el.getAttribute( 'moment-index' ) !== null ||
+ el.getAttribute( 'register-index' ) !== null
+ )
+ )
+ }),
+ highlightByQuery = function( query ){
+
+ Array.from( boardContainerEl.querySelectorAll( query ))
+ .forEach( function( el ){
+
+ el.classList.add( 'Q-circuit-cell-highlighted' )
+ })
+ }
+
+
+ // If we’ve found one of these “sticky” cells
+ // let’s use its moment and/or register data
+ // to highlight moments or registers (or all).
+
+ if( cellEl ){
+
+ const
+ momentIndex = cellEl.getAttribute( 'moment-index' ),
+ registerIndex = cellEl.getAttribute( 'register-index' )
+
+ if( momentIndex === null ){
+
+ highlightByQuery( `div[register-index="${ registerIndex }"]` )
+ return
+ }
+ if( registerIndex === null ){
+
+ highlightByQuery( `div[moment-index="${ momentIndex }"]` )
+ return
+ }
+ highlightByQuery(`
+
+ .Q-circuit-board-background > div[moment-index],
+ .Q-circuit-board-foreground > .Q-circuit-operation
+
+ `)
+ return
+ }
+
+
+ // Ok, we know we’re hovering over the circuit board
+ // but we’re not on a “sticky” cell.
+ // We might be over an operation, but we might not.
+ // No matter -- we’ll infer the moment and register indices
+ // from the cursor position.
+
+ const
+ boardElBounds = boardContainerEl.getBoundingClientRect(),
+ xLocal = x - boardElBounds.left + boardContainerEl.scrollLeft + 1,
+ yLocal = y - boardElBounds.top + boardContainerEl.scrollTop + 1,
+ columnIndex = Editor.pointToGrid( xLocal ),
+ rowIndex = Editor.pointToGrid( yLocal ),
+ momentIndex = Editor.gridColumnToMomentIndex( columnIndex ),
+ registerIndex = Editor.gridRowToRegisterIndex( rowIndex )
+
+
+ // If this hover is “out of bounds”
+ // ie. on the same row or column as an “Add register” or “Add moment” button
+ // then let’s not highlight anything.
+
+ if( momentIndex > circuitEl.circuit.timewidth ||
+ registerIndex > circuitEl.circuit.bandwidth ) return
+
+
+ // If we’re at 0, 0 or below that either means
+ // we’re over the “Select all” button (already taken care of above)
+ // or over the lock toggle button.
+ // Either way, it’s time to bail.
+
+ if( momentIndex < 1 || registerIndex < 1 ) return
+
+
+ // If we’ve made it this far that means
+ // we have valid moment and register indices.
+ // Highlight them!
+
+ highlightByQuery(`
+
+ div[moment-index="${ momentIndex }"],
+ div[register-index="${ registerIndex }"]
+ `)
+ return
+}
+
+
+
+ ///////////////////////
+ // //
+ // Pointer PRESS //
+ // //
+///////////////////////
+
+
+Editor.onPointerPress = function( event ){
+ // This is just a safety net
+ // in case something terrible has ocurred.
+ // (ex. Did the user click and then their mouse ran
+ // outside the window but browser didn’t catch it?)
+
+ if( Editor.dragEl !== null ){
+
+ Editor.onPointerRelease( event )
+ return
+ }
+ const
+ targetEl = event.target,
+ circuitEl = targetEl.closest( '.Q-circuit' ),
+ paletteEl = targetEl.closest( '.Q-circuit-palette' )
+ parameterEl = targetEl.closest( '.Q-parameters-box' )
+
+ // If we can’t find a circuit that’s a really bad sign
+ // considering this event should be fired when a circuit
+ // is clicked on. So... bail!
+
+ if( !circuitEl && !paletteEl ) return
+
+ // This is a bit of a gamble.
+ // There’s a possibility we’re not going to drag anything,
+ // but we’ll prep these variables here anyway
+ // because both branches of if( circuitEl ) and if( paletteEl )
+ // below will have access to this scope.
+
+ dragEl = document.createElement( 'div' )
+ dragEl.classList.add( 'Q-circuit-clipboard' )
+ const { x, y } = Editor.getInteractionCoordinates( event )
+
+
+ // Are we dealing with a circuit interface?
+ // ie. NOT a palette interface.
+
+ if( circuitEl && !parameterEl ){
+
+ // Shall we toggle the circuit lock?
+
+ const
+ circuit = circuitEl.circuit,
+ circuitIsLocked = circuitEl.classList.contains( 'Q-circuit-locked' ),
+ lockEl = targetEl.closest( '.Q-circuit-toggle-lock' )
+
+ if( lockEl ){
+
+ // const toolbarEl = Array.from( circuitEl.querySelectorAll( '.Q-circuit-button' ))
+ if( circuitIsLocked ){
+
+ circuitEl.classList.remove( 'Q-circuit-locked' )
+ lockEl.innerText = '🔓'
+ }
+ else {
+
+ circuitEl.classList.add( 'Q-circuit-locked' )
+ lockEl.innerText = '🔒'
+ Editor.unhighlightAll( circuitEl )
+ }
+
+
+ // We’ve toggled the circuit lock button
+ // so we should prevent further propagation
+ // before proceeding further.
+ // That includes running all this code again
+ // if it was originally fired by a mouse event
+ // and about to be fired by a touch event!
+
+ event.preventDefault()
+ event.stopPropagation()
+ return
+ }
+
+
+ // If our circuit is already “locked”
+ // then there’s nothing more to do here.
+
+ if( circuitIsLocked ) {
+
+ logger.warn( `User attempted to interact with a circuit editor but it was locked.` )
+ return
+ }
+
+
+ const
+ cellEl = targetEl.closest(`
+
+ .Q-circuit-board-foreground > div,
+ .Q-circuit-palette > div
+ `),
+ undoEl = targetEl.closest( '.Q-circuit-button-undo' ),
+ redoEl = targetEl.closest( '.Q-circuit-button-redo' ),
+ controlEl = targetEl.closest( '.Q-circuit-toggle-control' ),
+ swapEl = targetEl.closest( '.Q-circuit-toggle-swap' ),
+ addMomentEl = targetEl.closest( '.Q-circuit-moment-add' ),
+ addRegisterEl = targetEl.closest( '.Q-circuit-register-add' )
+
+ if( !cellEl &&
+ !undoEl &&
+ !redoEl &&
+ !controlEl &&
+ !swapEl &&
+ !addMomentEl &&
+ !addRegisterEl ) return
+
+
+ // By this point we know that the circuit is unlocked
+ // and that we’ll activate a button / drag event / etc.
+ // So we need to hault futher event propagation
+ // including running this exact code again if this was
+ // fired by a touch event and about to again by mouse.
+ // This may SEEM redundant because we did this above
+ // within the lock-toggle button code
+ // but we needed to NOT stop propagation if the circuit
+ // was already locked -- for scrolling and such.
+
+ event.preventDefault()
+ event.stopPropagation()
+
+
+ if( undoEl && circuit.history.undo$() ){
+
+ Editor.onSelectionChanged( circuitEl )
+ Editor.onCircuitChanged( circuitEl )
+ }
+ if( redoEl && circuit.history.redo$() ){
+
+ Editor.onSelectionChanged( circuitEl )
+ Editor.onCircuitChanged( circuitEl )
+ }
+ if( controlEl ) Editor.createControl( circuitEl )
+ if( swapEl ) Editor.createSwap( circuitEl )
+ if( addMomentEl ) console.log( '→ Add moment' )
+ if( addRegisterEl ) console.log( '→ Add register' )
+
+
+ // We’re done dealing with external buttons.
+ // So if we can’t find a circuit CELL
+ // then there’s nothing more to do here.
+
+ if( !cellEl ) return
+
+ // Once we know what cell we’ve pressed on
+ // we can get the momentIndex and registerIndex
+ // from its pre-defined attributes.
+ // NOTE that we are getting CSS grid column and row
+ // from our own conversion function and NOT from
+ // asking its styles. Why? Because browsers convert
+ // grid commands to a shorthand less easily parsable
+ // and therefore makes our code and reasoning
+ // more prone to quirks / errors. Trust me!
+
+ const
+ momentIndex = +cellEl.getAttribute( 'moment-index' ),
+ registerIndex = +cellEl.getAttribute( 'register-index' ),
+ columnIndex = Editor.momentIndexToGridColumn( momentIndex ),
+ rowIndex = Editor.registerIndexToGridRow( registerIndex )
+
+
+ // Looks like our circuit is NOT locked
+ // and we have a valid circuit CELL
+ // so let’s find everything else we could need.
+
+ const
+ selectallEl = targetEl.closest( '.Q-circuit-selectall' ),
+ registersymbolEl = targetEl.closest( '.Q-circuit-register-label' ),
+ momentsymbolEl = targetEl.closest( '.Q-circuit-moment-label' ),
+ inputEl = targetEl.closest( '.Q-circuit-input' ),
+ operationEl = targetEl.closest( '.Q-circuit-operation' )
+
+ // +++++++++++++++
+ // We’ll have to add some input editing capability later...
+ // Of course you can already do this in code!
+ // For now though most quantum code assumes all qubits
+ // begin with a value of zero so this is mostly ok ;)
+
+ if( inputEl ){
+
+ console.log( '→ Edit input Qubit value at', registerIndex )
+ return
+ }
+
+
+ // Let’s inspect a group of items via a CSS query.
+ // If any of them are NOT “selected” (highlighted)
+ // then select them all.
+ // But if ALL of them are already selected
+ // then UNSELECT them all.
+
+ function toggleSelection( query ){
+
+ const
+ operations = Array.from( circuitEl.querySelectorAll( query )),
+ operationsSelectedLength = operations.reduce( function( sum, element ){
+
+ sum += +element.classList.contains( 'Q-circuit-cell-selected' )
+ return sum
+
+ }, 0 )
+
+ if( operationsSelectedLength === operations.length ){
+
+ operations.forEach( function( el ){
+ el.classList.remove( 'Q-circuit-cell-selected' )
+ })
+ }
+ else {
+
+ operations.forEach( function( el ){
+
+ el.classList.add( 'Q-circuit-cell-selected' )
+ })
+ }
+ Editor.onSelectionChanged( circuitEl )
+ }
+
+
+ // Clicking on the “selectAll” button
+ // or any of the Moment symbols / Register symbols
+ // causes a selection toggle.
+ // In the future we may want to add
+ // dragging of entire Moment columns / Register rows
+ // to splice them out / insert them elsewhere
+ // when a user clicks and drags them.
+
+ if( selectallEl ){
+
+ toggleSelection( '.Q-circuit-operation' )
+ return
+ }
+ if( momentsymbolEl ){
+
+ toggleSelection( `.Q-circuit-operation[moment-index="${ momentIndex }"]` )
+ return
+ }
+ if( registersymbolEl ){
+
+ toggleSelection( `.Q-circuit-operation[register-index="${ registerIndex }"]` )
+ return
+ }
+
+
+ // Right here we can made a big decision:
+ // If you’re not pressing on an operation
+ // then GO HOME.
+
+ if( !operationEl ) return
+ // If we've doubleclicked on an operation and the operation has parameters, we should be able
+ // to edit those parameters regardless of whether or not the circuit is locked.
+ if( event.detail == 2) {
+ const operation = Gate.findBySymbol(operationEl.getAttribute( 'gate-symbol' ))
+ if( operation.has_parameters ) {
+ Editor.onDoubleclick( event, operationEl )
+ return
+ }
+ }
+
+ // Ok now we know we are dealing with an operation.
+ // This preserved selection state information
+ // will be useful for when onPointerRelease is fired.
+
+ if( operationEl.classList.contains( 'Q-circuit-cell-selected' )){
+ operationEl.wasSelected = true
+ }
+ else operationEl.wasSelected = false
+
+
+ // And now we can proceed knowing that
+ // we need to select this operation
+ // and possibly drag it
+ // as well as any other selected operations.
+
+ operationEl.classList.add( 'Q-circuit-cell-selected' )
+ const selectedOperations = Array.from( circuitEl.querySelectorAll( '.Q-circuit-cell-selected' ))
+ dragEl.circuitEl = circuitEl
+ dragEl.originEl = circuitEl.querySelector( '.Q-circuit-board-foreground' )
+
+
+ // These are the default values;
+ // will be used if we’re only dragging one operation around.
+ // But if dragging more than one operation
+ // and we’re dragging the clipboard by an operation
+ // that is NOT in the upper-left corner of the clipboard
+ // then we need to know what the offset is.
+ // (Will be calculated below.)
+
+ dragEl.columnIndexOffset = 1
+ dragEl.rowIndexOffset = 1
+
+
+ // Now collect all of the selected operations,
+ // rip them from the circuit board’s foreground layer
+ // and place them on the clipboard.
+
+ let
+ columnIndexMin = Infinity,
+ rowIndexMin = Infinity
+
+ selectedOperations.forEach( function( el ){
+
+
+ // WORTH REPEATING:
+ // Once we know what cell we’ve pressed on
+ // we can get the momentIndex and registerIndex
+ // from its pre-defined attributes.
+ // NOTE that we are getting CSS grid column and row
+ // from our own conversion function and NOT from
+ // asking its styles. Why? Because browsers convert
+ // grid commands to a shorthand less easily parsable
+ // and therefore makes our code and reasoning
+ // more prone to quirks / errors. Trust me!
+
+ const
+ momentIndex = +el.getAttribute( 'moment-index' ),
+ registerIndex = +el.getAttribute( 'register-index' ),
+ columnIndex = Editor.momentIndexToGridColumn( momentIndex ),
+ rowIndex = Editor.registerIndexToGridRow( registerIndex )
+
+ columnIndexMin = Math.min( columnIndexMin, columnIndex )
+ rowIndexMin = Math.min( rowIndexMin, rowIndex )
+ el.classList.remove( 'Q-circuit-cell-selected' )
+ el.origin = { momentIndex, registerIndex, columnIndex, rowIndex }
+ dragEl.appendChild( el )
+ })
+ selectedOperations.forEach( function( el ){
+
+ const
+ columnIndexForClipboard = 1 + el.origin.columnIndex - columnIndexMin,
+ rowIndexForClipboard = 1 + el.origin.rowIndex - rowIndexMin
+
+ el.style.gridColumn = columnIndexForClipboard
+ el.style.gridRow = rowIndexForClipboard
+
+
+ // If this operation element is the one we grabbed
+ // (mostly relevant if we’re moving multiple operations at once)
+ // we need to know what the “offset” so everything can be
+ // placed correctly relative to this drag-and-dropped item.
+
+ if( el.origin.columnIndex === columnIndex &&
+ el.origin.rowIndex === rowIndex ){
+
+ dragEl.columnIndexOffset = columnIndexForClipboard
+ dragEl.rowIndexOffset = rowIndexForClipboard
+ }
+ })
+
+
+ // We need an XY offset that describes the difference
+ // between the mouse / finger press position
+ // and the clipboard’s intended upper-left position.
+ // To do that we need to know the press position (obviously!),
+ // the upper-left bounds of the circuit board’s foreground,
+ // and the intended upper-left bound of clipboard.
+
+ const
+ boardEl = circuitEl.querySelector( '.Q-circuit-board-foreground' ),
+ bounds = boardEl.getBoundingClientRect(),
+ minX = Editor.gridToPoint( columnIndexMin ),
+ minY = Editor.gridToPoint( rowIndexMin )
+
+ dragEl.offsetX = bounds.left + minX - x
+ dragEl.offsetY = bounds.top + minY - y
+ dragEl.momentIndex = momentIndex
+ dragEl.registerIndex = registerIndex
+ }
+ else if( paletteEl ){
+ const operationEl = targetEl.closest( '.Q-circuit-operation' )
+
+ if( !operationEl ) return
+
+ const
+ bounds = operationEl.getBoundingClientRect(),
+ { x, y } = Editor.getInteractionCoordinates( event )
+
+ dragEl.appendChild( operationEl.cloneNode( true ))
+ dragEl.originEl = paletteEl
+ dragEl.offsetX = bounds.left - x
+ dragEl.offsetY = bounds.top - y
+ }
+ else if( parameterEl ){
+ const exitEl = targetEl.closest( '.Q-parameter-box-exit' )
+ if( !exitEl ) return
+ parameterEl.style.display = 'none'
+ const foregroundEl = circuitEl.querySelector( '.Q-circuit-board-foreground' )
+ operationEl = foregroundEl.querySelector( `[moment-index="${ parameterEl.getAttribute( 'operation-moment-index' )}"]` +
+ `[register-index="${ parameterEl.getAttribute( 'operation-register-index' )}"]` )
+ parameters = {}
+ operationSkeleton = Gate.findBySymbol( operationEl.getAttribute( 'gate-symbol' ))
+ Object.keys( operationSkeleton.parameters ).forEach( key => {
+ parameters[ key ] = operationEl.getAttribute( key ) ? operationEl.getAttribute( key ) : operationSkeleton.parameters[ key ]
+ })
+ //upon exiting, we should update the circuit!!!
+ circuitEl.circuit.set$(
+ operationEl.getAttribute( 'gate-symbol' ),
+ +operationEl.getAttribute( 'moment-index' ),
+ operationEl.getAttribute( 'register-indices' ) ? operationEl.getAttribute( 'register-indices' ).split(',').map( i => +i ) :
+ [ +operationEl.getAttribute( 'register-index' )],
+ parameters
+ )
+ //on exiting the parameter-input-box, we should update the circuit!!
+ parameterEl.innerHTML = ""
+ return
+ }
+ dragEl.timestamp = Date.now()
+
+
+ // Append the clipboard to the document,
+ // establish a global reference to it,
+ // and trigger a draw of it in the correct spot.
+
+ document.body.appendChild( dragEl )
+ Editor.dragEl = dragEl
+ Editor.onPointerMove( event )
+}
+
+
+
+
+
+
+ /////////////////////////
+ // //
+ // Pointer RELEASE //
+ // //
+/////////////////////////
+
+
+Editor.onPointerRelease = function( event ){
+
+
+ // If there’s no dragEl then bail immediately.
+ if( Editor.dragEl === null ) return
+ // Looks like we’re moving forward with this plan,
+ // so we’ll take control of the input now.
+ event.preventDefault()
+ event.stopPropagation()
+
+
+ // We can’t get the drop target from the event.
+ // Think about it: What was under the mouse / finger
+ // when this drop event was fired? THE CLIPBOARD !
+ // So instead we need to peek at what elements are
+ // under the mouse / finger, skipping element [0]
+ // because that will be the clipboard.
+
+ const { x, y } = Editor.getInteractionCoordinates( event )
+ const boardContainerAll = document.querySelectorAll(".Q-circuit-board-container")
+ if( boardContainerAll.length === 0 ) return
+ let boardContainerEl = Array.from(boardContainerAll).find((element) => {
+ let rect = element.getBoundingClientRect()
+ let clientX = rect.left
+ let clientY = rect.top
+ let height = element.offsetHeight
+ let width = element.offsetWidth
+ return ( x >= clientX && x <= clientX + width ) && ( y >= clientY && y <= clientY + height )
+ })
+ returnToOrigin = function(){
+
+
+ // We can only do a “true” return to origin
+ // if we were dragging from a circuit.
+ // If we were dragging from a palette
+ // we can just stop dragging.
+
+ if( Editor.dragEl.circuitEl ){
+
+ Array.from( Editor.dragEl.children ).forEach( function( el ){
+
+ Editor.dragEl.originEl.appendChild( el )
+ el.style.gridColumn = el.origin.columnIndex
+ el.style.gridRow = el.origin.rowIndex
+ if( el.wasSelected === true ) el.classList.remove( 'Q-circuit-cell-selected' )
+ else el.classList.add( 'Q-circuit-cell-selected' )
+ })
+ Editor.onSelectionChanged( Editor.dragEl.circuitEl )
+ }
+ document.body.removeChild( Editor.dragEl )
+ Editor.dragEl = null
+ }
+
+
+ // If we have not dragged on to a circuit board
+ // then we’re throwing away this operation.
+
+ if( !boardContainerEl ){
+
+ if( Editor.dragEl.circuitEl ){
+
+ const
+ originCircuitEl = Editor.dragEl.circuitEl
+ originCircuit = originCircuitEl.circuit
+
+ originCircuit.history.createEntry$()
+ Array
+ .from( Editor.dragEl.children )
+ .forEach( function( child ){
+
+ originCircuit.clear$(
+
+ child.origin.momentIndex,
+ child.origin.registerIndex
+ )
+ })
+ Editor.onSelectionChanged( originCircuitEl )
+ Editor.onCircuitChanged( originCircuitEl )
+ }
+
+
+ // TIME TO DIE.
+ // Let’s keep a private reference to
+ // the current clipboard.
+
+ let clipboardToDestroy = Editor.dragEl
+
+
+ // Now we can remove our dragging reference.
+
+ Editor.dragEl = null
+
+
+ // Add our CSS animation routine
+ // which will run for 1 second.
+ // If we were SUPER AWESOME
+ // we would have also calculated drag momentum
+ // and we’d let this glide away!
+
+ clipboardToDestroy.classList.add( 'Q-circuit-clipboard-destroy' )
+
+
+ // And around the time that animation is completing
+ // we can go ahead and remove our clipboard from the DOM
+ // and kill the reference.
+
+ setTimeout( function(){
+
+ document.body.removeChild( clipboardToDestroy )
+ clipboardToDestroy = null
+
+ }, 500 )
+
+
+ // No more to do here. Goodbye.
+
+ return
+ }
+
+
+ // If we couldn’t determine a circuitEl
+ // from the drop target,
+ // or if there is a target circuit but it’s locked,
+ // then we need to return these dragged items
+ // to their original circuit.
+
+ const circuitEl = boardContainerEl.closest( '.Q-circuit' )
+ if( circuitEl.classList.contains( 'Q-circuit-locked' )){
+
+ returnToOrigin()
+ return
+ }
+
+
+ // Time to get serious.
+ // Where exactly are we dropping on to this circuit??
+
+ const
+ circuit = circuitEl.circuit,
+ bounds = boardContainerEl.getBoundingClientRect(),
+ droppedAtX = x - bounds.left + boardContainerEl.scrollLeft,
+ droppedAtY = y - bounds.top + boardContainerEl.scrollTop,
+ droppedAtMomentIndex = Editor.gridColumnToMomentIndex(
+
+ Editor.pointToGrid( droppedAtX )
+ ),
+ droppedAtRegisterIndex = Editor.gridRowToRegisterIndex(
+
+ Editor.pointToGrid( droppedAtY )
+ ),
+ foregroundEl = circuitEl.querySelector( '.Q-circuit-board-foreground' )
+
+
+ // If this is a self-drop
+ // we can also just return to origin and bail.
+
+ if( Editor.dragEl.circuitEl === circuitEl &&
+ Editor.dragEl.momentIndex === droppedAtMomentIndex &&
+ Editor.dragEl.registerIndex === droppedAtRegisterIndex ){
+
+ returnToOrigin()
+ return
+ }
+
+
+ // Is this a valid drop target within this circuit?
+
+ if(
+ droppedAtMomentIndex < 1 ||
+ droppedAtMomentIndex > circuit.timewidth ||
+ droppedAtRegisterIndex < 1 ||
+ droppedAtRegisterIndex > circuit.bandwidth
+ ){
+ returnToOrigin()
+ return
+ }
+
+
+ // Finally! Work is about to be done!
+ // All we need to do is tell the circuit itself
+ // where we need to place these dragged items.
+ // It will do all the validation for us
+ // and then fire events that will place new elements
+ // where they need to go!
+
+ const
+ draggedOperations = Array.from( Editor.dragEl.children ),
+ draggedMomentDelta = droppedAtMomentIndex - Editor.dragEl.momentIndex,
+ draggedRegisterDelta = droppedAtRegisterIndex - Editor.dragEl.registerIndex,
+ setCommands = []
+
+
+ // Whatever the next action is that we perform on the circuit,
+ // this was user-initiated via the graphic user interface (GUI).
+
+ circuit.history.createEntry$()
+
+
+ // Now let’s work our way through each of the dragged operations.
+ // If some of these are components of a multi-register operation
+ // the sibling components will get spliced out of the array
+ // to avoid processing any specific operation more than once.
+
+ draggedOperations.forEach( function( childEl, i ){
+
+ let
+ momentIndexTarget = droppedAtMomentIndex,
+ registerIndexTarget = droppedAtRegisterIndex
+
+ if( Editor.dragEl.circuitEl ){
+
+ momentIndexTarget += childEl.origin.momentIndex - Editor.dragEl.momentIndex
+ registerIndexTarget += childEl.origin.registerIndex - Editor.dragEl.registerIndex
+ }
+
+
+ // Is this a multi-register operation?
+ // If so, this is also a from-circuit drop
+ // rather than a from-palette drop.
+
+ const registerIndicesString = childEl.getAttribute( 'register-indices' )
+ if( registerIndicesString ){
+
+ // What are ALL of the registerIndices
+ // associated with this multi-register operation?
+ // (We may use them later as a checklist.)
+
+ const
+ registerIndices = registerIndicesString
+ .split( ',' )
+ .map( function( str ){ return +str }),
+
+
+ // Lets look for ALL of the sibling components of this operation.
+ // Later we’ll check and see if the length of this array
+ // is equal to the total number of components for this operation.
+ // If they’re equal then we know we’re dragging the WHOLE thing.
+ // Otherwise we need to determine if it needs to break apart
+ // and if so, what that nature of that break might be.
+
+ foundComponents = Array.from(
+
+ Editor.dragEl.querySelectorAll(
+
+ `[moment-index="${ childEl.origin.momentIndex }"]`+
+ `[register-indices="${ registerIndicesString }"]`
+ )
+ )
+ .sort( function( a, b ){
+
+ const
+ aRegisterIndicesIndex = +a.getAttribute( 'register-indices-index' ),
+ bRegisterIndicesIndex = +b.getAttribute( 'register-indices-index' )
+
+ return aRegisterIndicesIndex - bRegisterIndicesIndex
+ }),
+ allComponents = Array.from( Editor.dragEl.circuitEl.querySelectorAll(
+
+ `[moment-index="${ childEl.origin.momentIndex }"]`+
+ `[register-indices="${ registerIndicesString }"]`
+ )),
+ remainingComponents = allComponents.filter( function( componentEl, i ){
+
+ return !foundComponents.includes( componentEl )
+ }),
+
+
+ // We can’t pick the gate symbol
+ // off the 0th gate in the register indices array
+ // because that will be an identity / control / null gate.
+ // We need to look at slot 1.
+
+ component1 = Editor.dragEl.querySelector(
+
+ `[moment-index="${ childEl.origin.momentIndex }"]`+
+ `[register-index="${ registerIndices[ 1 ] }"]`
+ ),
+ gatesymbol = component1 ?
+ component1.getAttribute( 'gate-symbol' ) :
+ childEl.getAttribute( 'gate-symbol' )
+
+
+ // We needed to grab the above gatesymbol information
+ // before we sent any clear$ commands
+ // which would in turn delete those componentEls.
+ // We’ve just completed that,
+ // so now’s the time to send a clear$ command
+ // before we do any set$ commands.
+
+ draggedOperations.forEach( function( childEl ){
+
+ Editor.dragEl.circuitEl.circuit.clear$(
+
+ childEl.origin.momentIndex,
+ childEl.origin.registerIndex
+ )
+ })
+
+
+ // FULL MULTI-REGISTER DRAG (TO ANY POSITION ON ANY CIRCUIT).
+ // If we are dragging all of the components
+ // of a multi-register operation
+ // then we are good to go.
+
+ if( registerIndices.length === foundComponents.length ){
+
+ const operationSkeleton = Gate.findBySymbol( gatesymbol )
+ parameters = {}
+ if( operationSkeleton.has_parameters ) {
+ Object.keys( operationSkeleton.parameters ).forEach( key => {
+ parameters[ key ] = childEl.getAttribute( key ) ? childEl.getAttribute( key ) : operationSkeleton.parameters[ key ]
+ })
+ }
+ //circuit.set$(
+ setCommands.push([
+
+ gatesymbol,
+ momentIndexTarget,
+
+
+ // We need to remap EACH register index here
+ // according to the drop position.
+ // Let’s let set$ do all the validation on this.
+
+ registerIndices.map( function( registerIndex ){
+
+ const siblingDelta = registerIndex - childEl.origin.registerIndex
+ registerIndexTarget = droppedAtRegisterIndex
+ if( Editor.dragEl.circuitEl ){
+
+ registerIndexTarget += childEl.origin.registerIndex - Editor.dragEl.registerIndex + siblingDelta
+ }
+ return registerIndexTarget
+ }),
+ parameters
+ // )
+ ])
+ }
+
+
+ // IN-MOMENT (IN-CIRCUIT) PARTIAL MULTI-REGISTER DRAG.
+ // It appears we are NOT dragging all components
+ // of a multi-register operation.
+ // But if we’re dragging within the same circuit
+ // and we’re staying within the same moment index
+ // that might be ok!
+
+ else if( Editor.dragEl.circuitEl === circuitEl &&
+ momentIndexTarget === childEl.origin.momentIndex ){
+
+
+ // We must ensure that only one component
+ // can sit at each register index.
+ // This copies registerIndices,
+ // but inverts the key : property relationship.
+ const registerMap = registerIndices
+ .reduce( function( registerMap, registerIndex, r ){
+
+ registerMap[ registerIndex ] = r
+ return registerMap
+
+ }, {} )
+
+
+ // First, we must remove each dragged component
+ // from the register it was sitting at.
+
+ foundComponents.forEach( function( component ){
+
+ const componentRegisterIndex = +component.getAttribute( 'register-index' )
+
+
+ // Remove this component from
+ // where this component used to be.
+
+ delete registerMap[ componentRegisterIndex ]
+ })
+
+
+ // Now we can seat it at its new position.
+ // Note: This may OVERWRITE one of its siblings!
+ // And that’s ok.
+ foundComponents.forEach( function( component ){
+
+ const
+ componentRegisterIndex = +component.getAttribute( 'register-index' ),
+ registerGrabDelta = componentRegisterIndex - Editor.dragEl.registerIndex
+
+
+ // Now put it where it wants to go,
+ // possibly overwriting a sibling component!
+ //ltnln: if a multiqubit operation component that requires a sibling, overwrites its sibling, both/all components should be destroyed
+ registerMap[
+
+ componentRegisterIndex + draggedRegisterDelta
+
+ ] = +component.getAttribute( 'register-indices-index' )
+ })
+
+
+ // Now let’s flip that registerMap
+ // back into an array of register indices.
+
+ const fixedRegistersIndices = Object.entries( registerMap )
+ .reduce( function( registers, entry, i ){
+
+ registers[ +entry[ 1 ]] = +entry[ 0 ]
+ return registers
+
+ }, [] )
+
+
+ // This will remove any blank entries in the array
+ // ie. if a dragged sibling overwrote a seated one.
+
+ .filter( function( entry ){
+ return mathf.isUsefulInteger( entry )
+ })
+
+ const operationSkeleton = Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ) )
+ parameters = {}
+ if( operationSkeleton.has_parameters ) {
+ Object.keys( operationSkeleton.parameters ).forEach( key => {
+ parameters[ key ] = childEl.getAttribute( key ) ? childEl.getAttribute( key ) : operationSkeleton.parameters[ key ]
+ })
+ }
+ // Finally, we’re ready to set.
+ // circuit.set$(
+ setCommands.push([
+ //ltnln: if a component of an operation that requires a sibling pair overwrites its sibling, we want it removed entirely.
+ fixedRegistersIndices.length < 2 && Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ) ).is_multi_qubit ?
+ Gate.NOOP :
+ childEl.getAttribute( 'gate-symbol' ),
+ momentIndexTarget,
+ fixedRegistersIndices,
+ parameters
+ // )
+ ])
+ }
+ else {
+ remainingComponents.forEach( function( componentEl, i ){
+ //circuit.set$(
+ const operationSkeleton = Gate.findBySymbol( componentEl.getAttribute( 'gate-symbol' ) )
+ parameters = {}
+ if( operationSkeleton.has_parameters ) {
+ Object.keys( operationSkeleton.parameters ).forEach( key => {
+ parameters[ key ] = +componentEl.getAttribute( key ) ? +componentEl.getAttribute( key ) : operationSkeleton.parameters[ key ]
+ })
+ }
+ setCommands.push([
+
+ +componentEl.getAttribute( 'register-indices-index' ) && !Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ) ).is_multi_qubit ?
+ gatesymbol :
+ Gate.NOOP,
+ +componentEl.getAttribute( 'moment-index' ),
+ +componentEl.getAttribute( 'register-index' ),
+ parameters
+ // )
+ ])
+ })
+
+
+ // Finally, let’s separate and update
+ // all the components that were part of the drag.
+
+ foundComponents.forEach( function( componentEl ){
+ const operationSkeleton = Gate.findBySymbol( componentEl.getAttribute( 'gate-symbol' ) )
+ parameters = {}
+ if( operationSkeleton.has_parameters ) {
+ Object.keys( operationSkeleton.parameters ).forEach( key => {
+ parameters[ key ] = +componentEl.getAttribute( key ) ? +componentEl.getAttribute( key ) : operationSkeleton.parameters[ key ]
+ })
+ }
+ setCommands.push([
+ //ltnln: temporary fix: certain multiqubit operations should only be represented in pairs of registers. If one is removed (i.e. a single component of the pair)
+ //then the entire operation should be removed.
+ +componentEl.getAttribute( 'register-indices-index' ) && !Gate.findBySymbol( componentEl.getAttribute( 'gate-symbol' ) ).is_multi_qubit ?
+ componentEl.getAttribute( 'gate-symbol' ) :
+ Gate.NOOP,
+ +componentEl.getAttribute( 'moment-index' ) + draggedMomentDelta,
+ +componentEl.getAttribute( 'register-index' ) + draggedRegisterDelta,
+ parameters
+ // )
+ ])
+ })
+ }
+
+
+ // We’ve just completed the movement
+ // of a multi-register operation.
+ // But all of the sibling components
+ // will also trigger this process
+ // unless we remove them
+ // from the draggd operations array.
+
+ let j = i + 1
+ while( j < draggedOperations.length ){
+
+ const possibleSibling = draggedOperations[ j ]
+ if( possibleSibling.getAttribute( 'gate-symbol' ) === gatesymbol &&
+ possibleSibling.getAttribute( 'register-indices' ) === registerIndicesString ){
+
+ draggedOperations.splice( j, 1 )
+ }
+ else j ++
+ }
+ }
+
+
+ // This is just a single-register operation.
+ // How simple this looks
+ // compared to all the gibberish above.
+
+ else {
+
+
+ // First, if this operation comes from a circuit
+ // (and not a circuit palette)
+ // make sure the old positions are cleared away.
+
+ if( Editor.dragEl.circuitEl ){
+
+ draggedOperations.forEach( function( childEl ){
+
+ Editor.dragEl.circuitEl.circuit.clear$(
+
+ childEl.origin.momentIndex,
+ childEl.origin.registerIndex
+ )
+ })
+ }
+
+
+ // And now set$ the operation
+ // in its new home.
+
+ // circuit.set$(
+ let registerIndices = [ registerIndexTarget ]
+ //ltnln: By default, multiqubit gates appear in pairs on the circuit rather than
+ // requiring the user to have to pair them like with Swap/CNot.
+ const operationSkeleton = Gate.findBySymbol( childEl.getAttribute( 'gate-symbol' ))
+ if(operationSkeleton.is_multi_qubit ) {
+ registerIndices.push( registerIndexTarget == circuit.bandwidth ? registerIndexTarget - 1 : registerIndexTarget + 1)
+ }
+ let parameters = {}
+ if( operationSkeleton.has_parameters ) {
+ Object.keys( operationSkeleton.parameters ).forEach( key => {
+ parameters[ key ] = childEl.getAttribute( key ) ? childEl.getAttribute( key ) : operationSkeleton.parameters[ key ]
+ })
+ }
+ setCommands.push([
+ childEl.getAttribute( 'gate-symbol' ),
+ momentIndexTarget,
+ registerIndices,
+ parameters
+ // )
+ ])
+ }
+ })
+
+
+ // DO IT DO IT DO IT
+
+ setCommands.forEach( function( setCommand ){
+
+ circuit.set$.apply( circuit, setCommand )
+ })
+
+
+ // Are we capable of making controls? Swaps?
+
+ Editor.onSelectionChanged( circuitEl )
+ Editor.onCircuitChanged( circuitEl )
+
+
+ // If the original circuit and destination circuit
+ // are not the same thing
+ // then we need to also eval the original circuit.
+
+ if( Editor.dragEl.circuitEl &&
+ Editor.dragEl.circuitEl !== circuitEl ){
+
+ const originCircuitEl = Editor.dragEl.circuitEl
+ Editor.onSelectionChanged( originCircuitEl )
+ Editor.onCircuitChanged( originCircuitEl )
+ }
+
+
+ // We’re finally done here.
+ // Clean up and go home.
+ // It’s been a long journey.
+ // I love you all.
+
+ document.body.removeChild( Editor.dragEl )
+ Editor.dragEl = null
+}
+
+
+ /////////////////////////
+ // //
+ // Pointer DOUBLECLICK //
+ // //
+/////////////////////////
+//ltnln: my trying out an idea for parameterized gates...
+Editor.onDoubleclick = function( event, operationEl ) {
+ const operation = Gate.findBySymbol( operationEl.getAttribute( 'gate-symbol' ))
+ const { x, y } = Editor.getInteractionCoordinates( event )
+ const boardContainerAll = document.querySelectorAll(".Q-circuit-board-container")
+ if( boardContainerAll.length === 0 ) return
+ let boardContainerEl = Array.from(boardContainerAll).find((element) => {
+ let rect = element.getBoundingClientRect()
+ let clientX = rect.left
+ let clientY = rect.top
+ let height = element.offsetHeight
+ let width = element.offsetWidth
+ return ( x >= clientX && x <= clientX + width ) && ( y >= clientY && y <= clientY + height )
+ })
+ if( !boardContainerEl ) return;
+ const parameterEl = boardContainerEl.querySelector('.Q-parameters-box')
+ const exit = Editor.createNewElement( 'button', parameterEl, 'Q-parameter-box-exit')
+ exit.appendChild(document.createTextNode( '⬅' ))
+ parameterEl.setAttribute( "operation-moment-index", operationEl.getAttribute( 'moment-index' ))
+ parameterEl.setAttribute( "operation-register-index", operationEl.getAttribute( 'register-index' ))
+ //here we generate queries for each parameter that the gate operation takes!
+ const parameters = Object.keys(operation.parameters)
+ parameters.forEach( element => {
+ if( operation.parameters && operation.parameters[element] !== null ) {
+ const input_fields = document.createElement( 'div' )
+ parameterEl.appendChild( input_fields )
+ input_fields.classList.add( 'Q-parameter-box-input-container' )
+
+ const label = Editor.createNewElement( "span", input_fields, 'Q-parameter-input-label' )
+ label.appendChild(document.createTextNode( element ))
+
+ const textbox = Editor.createNewElement( "input", input_fields, 'Q-parameter-box-input')
+ textbox.setAttribute( 'type', 'text' )
+ textbox.setAttribute( 'placeholder', element )
+ textbox.setAttribute( 'value', operationEl.getAttribute(element) ? operationEl.getAttribute(element) : operation.parameters[element] )
+ //set textbox to update the operation instance (cellEl)'s parameters on value change
+ textbox.addEventListener( "change", () => {
+ let parameterValue = +textbox.value;
+ let oldValue = operationEl.getAttribute( element )
+ if( !oldValue ) oldValue = operation.parameters[ element ]
+ if( parameterValue === null || parameterValue === Infinity ) textbox.value = oldValue.toString()
+ else {
+ operationEl.setAttribute( element, parameterValue )
+ textbox.value = parameterValue
+ }
+ })
+
+
+ }
+ })
+ parameterEl.classList.toggle('overlay')
+ parameterEl.style.display = 'block'
+}
+
+
+
+ ///////////////////
+ // //
+ // Listeners //
+ // //
+///////////////////
+
+
+// These listeners must be appliedm
+// to the entire WINDOW (and not just document.body!)
+
+window.addEventListener( 'mousemove', Editor.onPointerMove )
+window.addEventListener( 'touchmove', Editor.onPointerMove )
+window.addEventListener( 'mouseup', Editor.onPointerRelease )
+window.addEventListener( 'touchend', Editor.onPointerRelease )
+module.exports = {Editor}
\ No newline at end of file
diff --git a/build/q-old.css b/packages/quantum-js-vis/Q.css
similarity index 94%
rename from build/q-old.css
rename to packages/quantum-js-vis/Q.css
index e4a6620..ebacba8 100644
--- a/build/q-old.css
+++ b/packages/quantum-js-vis/Q.css
@@ -1,6 +1,6 @@
/*
- Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+ Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
*/
@charset "utf-8";
@@ -11,8 +11,8 @@
/*
This file is in the process of being separated
- in to “essential global Q.js styles†which will
- remain here in Q.css, and “documentation-specificâ€
+ in to “essential global Q.js styles” which will
+ remain here in Q.css, and “documentation-specific”
styles which will be removed from here and placed
within the /other/documentation.css file instead.
@@ -319,7 +319,7 @@ svg, :root {
/*
- The below still need to be prefaced with “Q-â€
+ The below still need to be prefaced with “Q-”
and for the HTML pages to be updated accordingly.
*/
@@ -341,8 +341,6 @@ svg, :root {
max-width: 100%;
overflow-x: auto;
font-family: var( --Q-font-family-sans );
- /*letter-spacing: 0.03em;*/
- word-spacing: 0.2em;
}
dd .maths {
@@ -395,23 +393,22 @@ dd .maths {
vertical-align: middle;
position: relative;
align: middle;
- margin: 1em 0.5em;
+ margin: 1em;
padding: 1em;
- /*font-family: var( --Q-font-family-mono );*/
+ font-family: var( --Q-font-family-mono );
font-weight: 300;
line-height: 1em;
- /*text-align: right;*/
- text-align: center;
+ text-align: right;
}
.matrix td {
- padding: 0.25em 0.5em;
+ padding: 5px 10px;
}
.matrix-bracket-left, .matrix-bracket-right {
position: absolute;
top: 0;
- width: 0.5em;
+ width: 5px;
height: 100%;
border: 1px solid #CCC;
}
@@ -440,7 +437,7 @@ dd .maths {
.Q-state-vector.bra::before,
.complex-vector.bra::before {
- content: '⟨';
+ content: '⟨';
color: #BBB;
}
.Q-state-vector.bra::after,
@@ -458,7 +455,7 @@ dd .maths {
.Q-state-vector.ket::after,
.complex-vector.ket::after {
- content: '⟩';
+ content: '⟩';
color: #BBB;
}
.Q-state-vector.bra + .Q-state-vector.ket::before,
@@ -475,7 +472,7 @@ dd .maths {
/*
- Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
+ Copyright © 2019–2020, Stewart Smith. See LICENSE for details.
*/
@charset "utf-8";
@@ -487,6 +484,7 @@ dd .maths {
+
/*
Z indices:
@@ -569,12 +567,11 @@ dd .maths {
-
.Q-circuit,
.Q-circuit-palette {
position: relative;
- width: 50%;
+ width: 100%;
}
.Q-circuit-palette {
@@ -598,7 +595,6 @@ dd .maths {
margin: 1rem 0 2rem 0;
/*border-top: 2px solid hsl( 0, 0%, 50% );*/
}
-.Q-parameters-box,
.Q-circuit-board-foreground {
line-height: 3.85rem;
@@ -751,19 +747,6 @@ dd .maths {
grid-auto-columns: 4rem;
grid-auto-flow: column;
}
-
-.Q-parameters-box {
-
- position: absolute;
- display: none;
- z-index: 100;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: whitesmoke;
-}
-
/*.Q-circuit-palette,*/
.Q-circuit-board-foreground,
.Q-circuit-board-background {
@@ -853,17 +836,8 @@ dd .maths {
);
}
-.Q-parameter-box-exit {
- position: relative;
- right: 0;
- left: 0;
- width: 5rem;
- height: 2.5rem;
- background-color: whitesmoke;
- border-radius: 0;
-}
-.Q-parameters-box > div,
+
.Q-circuit-palette > div,
.Q-circuit-clipboard > div,
.Q-circuit-board-foreground > div {
@@ -1105,7 +1079,7 @@ dd .maths {
rgba( 0, 0, 0, 0.05 )
)*/;
}
-.Q-parameter-box-exit .Q-circuit-palette .Q-circuit-operation:hover {
+.Q-circuit-palette .Q-circuit-operation:hover {
/*background-color: rgba( 255, 255, 255, 0.6 );*/
background-color: white;
@@ -1218,25 +1192,6 @@ dd .maths {
}
-.Q-parameter-box-input-container {
- position: relative;
- text-align: center;
- grid-auto-columns: 4rem;
- grid-auto-flow: column;
-}
-
-.Q-parameter-box-input {
- position: relative;
- border-radius: .2rem;
- margin-left: 10px;
- font-family: var( --Q-font-family-mono );
-}
-
-.Q-parameter-input-label {
- position: relative;
- color: var( --Q-color-blue );
- font-family: var( --Q-font-family-mono );
-}
diff --git a/packages/quantum-js-vis/index.js b/packages/quantum-js-vis/index.js
new file mode 100644
index 0000000..6838703
--- /dev/null
+++ b/packages/quantum-js-vis/index.js
@@ -0,0 +1,91 @@
+const {Editor} = require('./Q-Circuit-Editor');
+const {circuit} = require('quantum-js-util');
+const {BlochSphere} = require('./Q-BlochSphere');
+console.log("Welcome to Q.js! The GUI experience!\n");
+
+braket = function(){
+
+
+ // Create the HTML bits we need,
+ // contain them all together,
+ // and output them to Jupyter.
+ if( arguments.length === 0 || arguments.length > 3 ) return;
+ const element = arguments[0];
+ const args = (Array.from(arguments)).slice(1);
+ let circuit
+ if(args.length === 0) {
+ circuit = new Q( 4, 8 )
+ }
+ else if(args.length === 1) {
+ circuit = new Q( args[0] )
+ }
+ else {
+ circuit = new Q( args[0], args[1] )
+ }
+ container = document.createElement( 'div' )
+ let paletteEl = Editor.createPalette();
+ paletteEl.style.width = "50%";
+ container.appendChild( paletteEl );
+ container.appendChild( circuit.toDom() )
+ element.html( container )
+
+
+ // We’re going to take this SLOOOOOOOOWLY
+ // because there are many potential things to debug.
+
+ const thisCell = Jupyter.notebook.get_selected_cell()
+ // console.log( 'thisCell', thisCell )
+
+ const thisCellIndex = Jupyter.notebook.get_cells().indexOf( thisCell )
+ // console.log( 'thisCellIndex', thisCellIndex )
+
+ const nextCell = Jupyter.notebook.insert_cell_below( 'code', thisCellIndex - 1 )
+ const nextNextCell = Jupyter.notebook.insert_cell_below( 'markdown', Jupyter.notebook.get_cells().indexOf( thisCell ) - 1 )
+ // console.log( 'nextCell', nextCell )
+
+ nextCell.set_text( circuit.toAmazonBraket() )
+ nextNextCell.set_text( circuit.report$() )
+
+
+
+
+
+
+ window.addEventListener( 'Q gui altered circuit', function( event ){
+
+ // updatePlaygroundFromDom( event.detail.circuit )
+ if( event.detail.circuit === circuit ){
+
+ console.log( 'Updating circuit from GUI', circuit )
+ circuit.evaluate$()
+ nextCell.set_text( circuit.toAmazonBraket() )
+
+ }
+ })
+
+ window.addEventListener( 'Circuit.evaluate completed', function( event ) {
+ if( event.detail.circuit === circuit ) {
+ nextNextCell.set_text( circuit.report$() )
+ }
+ })
+
+
+
+ // nextCell.render()
+
+ // console.log( 'thisCell', thisCell )
+ // console.log( 'nextCell', nextCell )
+ // console.log( 'thisCellIndex', thisCellIndex )
+
+ // code = Jupyter.notebook.insert_cell_{0}('code');
+ // code.set_text(atob("{1}"))
+
+ // var t_cell = Jupyter.notebook.get_selected_cell()
+ // t_cell.set_text(' \\n{}')
+ // var t_index = Jupyter.notebook.get_cells().indexOf(t_cell)
+ // Jupyter.notebook.to_markdown(t_index)
+ // Jupyter.notebook.get_cell(t_index).render()
+}
+
+
+module.exports = {Editor, BlochSphere, braket};
\ No newline at end of file
diff --git a/packages/quantum-js-vis/package-lock.json b/packages/quantum-js-vis/package-lock.json
new file mode 100644
index 0000000..95e01ab
--- /dev/null
+++ b/packages/quantum-js-vis/package-lock.json
@@ -0,0 +1,35 @@
+{
+ "name": "quantum-js-vis",
+ "version": "1.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "version": "1.0.0",
+ "license": "ISC",
+ "devDependencies": {
+ "prettier": "^2.3.2"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
+ "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ }
+ },
+ "dependencies": {
+ "prettier": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
+ "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
+ "dev": true
+ }
+ }
+}
diff --git a/packages/quantum-js-vis/package.json b/packages/quantum-js-vis/package.json
new file mode 100644
index 0000000..f17495b
--- /dev/null
+++ b/packages/quantum-js-vis/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "quantum-js-vis",
+ "version": "1.0.0",
+ "description": "Visualization Components for Q.js",
+ "main": "index.js",
+ "scripts": {
+ "test": "jest",
+ "prettier": "npx prettier --write **/*.js" ,
+ "lint": "npx eslint **/*.js"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "prettier": "^2.3.2",
+ "window": "^4.2.7"
+
+ },
+ "dependencies": {
+ "requirejs": "^2.3.6"
+ }
+}
diff --git a/playground.html b/playground.html
index 10543ca..47aeead 100644
--- a/playground.html
+++ b/playground.html
@@ -40,20 +40,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
+
@@ -246,7 +239,7 @@ Whiplash
.from( document.querySelectorAll( '.Q-circuit-palette' ))
.forEach( function( el ){
- Q.Circuit.Editor.createPalette( el )
+ Editor.createPalette( el )
})
@@ -291,7 +284,7 @@ Whiplash
document.getElementById( 'playground-apply-button' ).setAttribute( 'disabled', 'disabled' )
const circuit = Q( text )
- if( circuit instanceof Q.Circuit ){//+++++ This validation appears broken!
+ if( circuit instanceof Circuit ){//+++++ This validation appears broken!
circuit.name = 'playground'
const domEl = document.getElementById( 'playground' )
@@ -371,7 +364,7 @@ Whiplash
// EVALUATION.
-window.addEventListener( 'Q.Circuit.evaluate began', function( event ){
+window.addEventListener( 'Circuit.evaluate began', function( event ){
console.log(
@@ -379,7 +372,7 @@ Whiplash
event.detail.circuit.toDiagram() +'\n\n'
)
})
-window.addEventListener( 'Q.Circuit.evaluate progressed', function( event ){
+window.addEventListener( 'Circuit.evaluate progressed', function( event ){
const
length = 20,
@@ -408,7 +401,7 @@ Whiplash
// console.log( 'state width', state.getWidth(), 'state height', state.getHeight() )
// console.log( 'state', state.toTsv() )
})
-window.addEventListener( 'Q.Circuit.evaluate completed', function( event ){
+window.addEventListener( 'Circuit.evaluate completed', function( event ){
console.log(
diff --git a/resources.html b/resources.html
index 21da767..bfb88dc 100644
--- a/resources.html
+++ b/resources.html
@@ -40,20 +40,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
+
diff --git a/tutorials.html b/tutorials.html
index 9d6a2e5..7218945 100644
--- a/tutorials.html
+++ b/tutorials.html
@@ -40,20 +40,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
+