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' - - }, '\n\t' ) + '\n\t' + // Validate our arguments. - }, '\n
'+ cell.toText() +'
' ) +'\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"; + }, "\n\t") + + "\n\t" + ); + }, "\n
" + cell.toText() + "
") + "\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": "![Quantum JavaScript (Q.js)](./assets/Q-mark.svg \"Quantum JavaScript (Q.js)\")", "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 @@ - - + + - - - - - - - - +