// Copyright © 2019–2020, Stewart Smith. See LICENSE for details. const Q = function(){ // Did we send arguments of the form // ( bandwidth, timewidth )? if( arguments.length === 2 && Array.from( arguments ).every( function( argument ){ return Q.isUsefulInteger( argument ) })){ return new Q.Circuit( arguments[ 0 ], arguments[ 1 ]) } // Otherwise assume we are creating a circuit // from a text block. return Q.Circuit.fromText( arguments[ 0 ]) } Object.assign( Q, { verbosity: 0.5, log: function( verbosityThreshold, ...remainingArguments ){ if( Q.verbosity >= verbosityThreshold ) console.log( ...remainingArguments ) return '(log)' }, warn: function(){ console.warn( ...arguments ) return '(warn)' }, error: function(){ console.error( ...arguments ) return '(error)' }, extractDocumentation: function( 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( '' ) }, help: function( f ){ if( f === undefined ) f = Q return Q.extractDocumentation( f ) }, 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 Q.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 ]) } }, isUsefulNumber: function( n ){ return isNaN( n ) === false && ( typeof n === 'number' || n instanceof Number ) && n !== Infinity && n !== -Infinity }, isUsefulInteger: function( n ){ return Q.isUsefulNumber( n ) && Number.isInteger( n ) }, loop: function(){}, 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 ) 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 ){ return ( Math.exp( n ) - Math.exp( -n )) / 2 }, hyperbolicCosine: function( n ){ return ( Math.exp( n ) + Math.exp( -n )) / 2 }, round: function( n, d ){ if( typeof d !== 'number' ) d = 0 const f = Math.pow( 10, d ) return Math.round( n * f ) / f }, toTitleCase: function( text ){ 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 = ' ' const padLengthLeft = Math.floor(( length - text.length ) / 2 ), padLengthRight = length - text.length - padLengthLeft return text .padStart( padLengthLeft + text.length, filler ) .padEnd( length, filler ) } else return text }, namesIndex: 0, shuffledNames: [], shuffleNames$: function(){ let m = [] for( let c = 0; c < Q.COLORS.length; c ++ ){ for( let a = 0; a < Q.ANIMALS.length; a ++ ){ m.push([ c, a, Math.random() ]) } } Q.shuffledNames = m.sort( function( a, b ){ 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 } }) Q.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 ] ) console.log( ` QQQQQQ QQ QQ QQ QQ QQ QQ QQ QQ QQ QQ QQ QQQQ ${Q.REVISION} https://quantumjavascript.app ` ) // Copyright © 2019–2020, Stewart Smith. See LICENSE for details. 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 … -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 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. ` if( real instanceof Q.ComplexNumber ){ 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 ++ } Object.assign( Q.ComplexNumber, { index: 0, help: function(){ return Q.help( this )}, constants: {}, createConstant: Q.createConstant, createConstants: Q.createConstants, 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 Q.ComplexNumber.operate( 'areEqual', a, b, function( a, b ){ return Math.abs( a - b ) < Q.EPSILON }, function( a, b ){ return ( Math.abs( a - b.real ) < Q.EPSILON && Math.abs( b.imaginary ) < Q.EPSILON ) }, function( a, b ){ return ( Math.abs( a.real - b ) < Q.EPSILON && Math.abs( a.imaginary ) < Q.EPSILON ) }, function( a, b ){ return ( Math.abs( a.real - b.real ) < Q.EPSILON && Math.abs( a.imaginary - b.imaginary ) < Q.EPSILON ) } ) }, absolute: function( n ){ return Q.hypotenuse( n.real, n.imaginary ) }, conjugate: function( n ){ 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 ) } else if( a instanceof Q.ComplexNumber ){ 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 ) } else return Q.error( 'Q.ComplexNumber attempted to', name, 'with something that is neither a Number or Q.ComplexNumber:', a ) }, sine: function( 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( 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( t1.real - b, t1.imaginary + a )) return new Q.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 Q.ComplexNumber( 0, Infinity ) if( b === -1 ) return new Q.ComplexNumber( 0, -Infinity ) } const d = a * a + ( 1 - b ) * ( 1 - b ), t = Q.ComplexNumber.log( new Q.ComplexNumber( ( 1 - b * b - a * a ) / d, a / d * -2 )) return new Q.ComplexNumber( t.imaginary / 2, t.real / 2 ) }, power: function( a, b ){ if( Q.ComplexNumber.isNumberLike( a )) a = new Q.ComplexNumber( a ) if( Q.ComplexNumber.isNumberLike( b )) b = new Q.ComplexNumber( b ) // Anything raised to the Zero power is 1. if( b.isZero() ) return Q.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 Q.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 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 )) } } } 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 return new Q.ComplexNumber( 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( 'multiply', a, b, function( a, b ){ return new Q.ComplexNumber( a * b ) }, function( a, b ){ return new Q.ComplexNumber( a * b.real, a * b.imaginary ) }, function( a, b ){ 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. return new Q.ComplexNumber( firsts + lasts, outers + inners ) } ) }, 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 ){ return new Q.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 Q.ComplexNumber.operate( 'add', a, b, function( a, b ){ return new Q.ComplexNumber( a + b ) }, function( a, b ){ return new Q.ComplexNumber( b.real + a, b.imaginary ) }, function( a, b ){ 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( 'subtract', a, b, function( a, b ){ return new Q.ComplexNumber( a - b ) }, function( a, b ){ return new Q.ComplexNumber( b.real - a, b.imaginary ) }, function( a, b ){ return new Q.ComplexNumber( a.real - b, a.imaginary ) }, function( a, b ){ return new Q.ComplexNumber( a.real - b.real, a.imaginary - b.imaginary ) } ) } }) 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 ) ) Object.assign( Q.ComplexNumber.prototype, { // NON-destructive operations. clone: function(){ return new Q.ComplexNumber( this.real, this.imaginary ) }, reduce: function(){ // Note: this *might* kill function chaining. if( this.imaginary === 0 ) return this.real return this }, isNaN: function( n ){ return Q.ComplexNumber.isNaN( this )// Returned boolean will kill function chaining. }, isZero: function( n ){ return Q.ComplexNumber.isZero( this )// Returned boolean will kill function chaining. }, isFinite: function( n ){ 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 Q.ComplexNumber.areEqual( this, b )// Returned boolean will kill function chaining. }, absolute: function(){ return Q.ComplexNumber.absolute( this )// Returned number will kill function chaining. }, conjugate: function(){ return Q.ComplexNumber.conjugate( this ) }, power: function( b ){ return Q.ComplexNumber.power( this, b ) }, squareRoot: function(){ return Q.ComplexNumber.squareRoot( this ) }, log: function(){ return Q.ComplexNumber.log( this ) }, 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 ) }, toText: function( roundToDecimal, padPositive ){ // 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 ? '' : ' ' // Right. Let’s copy over the actual numbers. let rNumber = this.real, iNumber = this.imaginary // 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 // 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 }, // 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(){ 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 )) } }) // Copyright © 2019–2020, Stewart Smith. See LICENSE for details. Q.Matrix = function(){ // We’re keeping track of how many matrices are // actually being generated. Just curiosity. this.index = Q.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 && Q.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 && 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 { // 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 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 ) } // 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 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 ) } // 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 ) } } /////////////////////////// // // // Static properties // // // /////////////////////////// Object.assign( Q.Matrix, { 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, 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 ){ 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 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 ) }, 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. from: function( format ){ 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 ){ return new Q.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). ` 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 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 ){ return Q.Matrix.fromXsv( html .replace( /\r?\n|\r|
'+ cell.toText() +' | ' }, '\n\t
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.
Q.log( 0.5,
`\n\nCreated a DOM interface for $('#${ this.domId }').circuit\n\n`,
circuit.toDiagram(),
'\n\n\n'
)
}
// Augment Q.Circuit to have this functionality.
Q.Circuit.toDom = function( circuit, targetEl ){
return new Q.Circuit.Editor( circuit, targetEl ).domElement
}
Q.Circuit.prototype.toDom = function( targetEl ){
return new Q.Circuit.Editor( this, targetEl ).domElement
}
Object.assign( Q.Circuit.Editor, {
index: 0,
help: function(){ return Q.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 * Q.Circuit.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 * Q.Circuit.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' ]
}
},
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
}
paletteEl.classList.add( 'Q-circuit-palette' )
'HXYZPT*'
.split( '' )
.forEach( function( symbol ){
const gate = Q.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 !== Q.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', Q.Circuit.Editor.onPointerPress )
paletteEl.addEventListener( 'touchstart', Q.Circuit.Editor.onPointerPress )
return paletteEl
}
})
/////////////////////////
// //
// Operation CLEAR //
// //
/////////////////////////
Q.Circuit.Editor.prototype.onExternalClear = function( event ){
if( event.detail.circuit === this.circuit ){
Q.Circuit.Editor.clear( this.domElement, {
momentIndex: event.detail.momentIndex,
registerIndices: event.detail.registerIndices
})
}
}
Q.Circuit.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 //
// //
///////////////////////
Q.Circuit.Editor.prototype.onExternalSet = function( event ){
if( event.detail.circuit === this.circuit ){
Q.Circuit.Editor.set( this.domElement, event.detail.operation )
}
}
Q.Circuit.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 = Q.Circuit.Editor.momentIndexToGridColumn( operation.momentIndex )
operationEl.style.gridRowStart = Q.Circuit.Editor.registerIndexToGridRow( registerIndex )
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
// 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 = Q.Circuit.Editor.registerIndexToGridRow( start )
containerEl.style.gridRowEnd = Q.Circuit.Editor.registerIndexToGridRow( end + 1 )
containerEl.style.gridColumn = Q.Circuit.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' )
}
})
}
Q.Circuit.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( !Q.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[ Q.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[ Q.Gate.CURSOR.symbol ] === undefined &&
Object.keys( gates ).length === 1 &&
totalControlled > 0 &&
totalNotControlled > 0 ){
return true
}
// Any other combination allowed? Nope!
return false
}
Q.Circuit.Editor.createControl = function( circuitEl ){
if( Q.Circuit.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' ) === Q.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.
Q.Circuit.Editor.onSelectionChanged( circuitEl )
Q.Circuit.Editor.onCircuitChanged( circuitEl )
return this
}
Q.Circuit.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' ) === Q.Gate.CURSOR.symbol
})
if( areBothCursors ) return true
// Otherwise this is not a valid swap candidate.
return false
}
Q.Circuit.Editor.createSwap = function( circuitEl ){
if( Q.Circuit.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$(
Q.Gate.SWAP,
momentIndex,
registerIndices
)
// Update our toolbar button states.
Q.Circuit.Editor.onSelectionChanged( circuitEl )
Q.Circuit.Editor.onCircuitChanged( circuitEl )
return this
}
Q.Circuit.Editor.onSelectionChanged = function( circuitEl ){
const controlButtonEl = circuitEl.querySelector( '.Q-circuit-toggle-control' )
if( Q.Circuit.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 )){
swapButtonEl.removeAttribute( 'Q-disabled' )
}
else swapButtonEl.setAttribute( 'Q-disabled', true )
}
Q.Circuit.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$() ) ??
}
Q.Circuit.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 //
// //
//////////////////////
Q.Circuit.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 } = Q.Circuit.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( Q.Circuit.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
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'
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 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 = Q.Circuit.Editor.pointToGrid( xLocal ),
rowIndex = Q.Circuit.Editor.pointToGrid( yLocal ),
momentIndex = Q.Circuit.Editor.gridColumnToMomentIndex( columnIndex ),
registerIndex = Q.Circuit.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 //
// //
///////////////////////
Q.Circuit.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( Q.Circuit.Editor.dragEl !== null ){
Q.Circuit.Editor.onPointerRelease( event )
return
}
const
targetEl = event.target,
circuitEl = targetEl.closest( '.Q-circuit' ),
paletteEl = targetEl.closest( '.Q-circuit-palette' )
// 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 } = Q.Circuit.Editor.getInteractionCoordinates( event )
// Are we dealing with a circuit interface?
// ie. NOT a palette interface.
if( circuitEl ){
// 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 = '🔒'
Q.Circuit.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 ) {
Q.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$() ){
Q.Circuit.Editor.onSelectionChanged( circuitEl )
Q.Circuit.Editor.onCircuitChanged( circuitEl )
}
if( redoEl && circuit.history.redo$() ){
Q.Circuit.Editor.onSelectionChanged( circuitEl )
Q.Circuit.Editor.onCircuitChanged( circuitEl )
}
if( controlEl ) Q.Circuit.Editor.createControl( circuitEl )
if( swapEl ) Q.Circuit.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 = Q.Circuit.Editor.momentIndexToGridColumn( momentIndex ),
rowIndex = Q.Circuit.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' )
})
}
Q.Circuit.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
// 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 = Q.Circuit.Editor.momentIndexToGridColumn( momentIndex ),
rowIndex = Q.Circuit.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 = Q.Circuit.Editor.gridToPoint( columnIndexMin ),
minY = Q.Circuit.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 } = Q.Circuit.Editor.getInteractionCoordinates( event )
dragEl.appendChild( operationEl.cloneNode( true ))
dragEl.originEl = paletteEl
dragEl.offsetX = bounds.left - x
dragEl.offsetY = bounds.top - y
}
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 )
Q.Circuit.Editor.dragEl = dragEl
Q.Circuit.Editor.onPointerMove( event )
}
/////////////////////////
// //
// Pointer RELEASE //
// //
/////////////////////////
Q.Circuit.Editor.onPointerRelease = function( event ){
// If there’s no dragEl then bail immediately.
if( Q.Circuit.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 } = Q.Circuit.Editor.getInteractionCoordinates( event ),
boardContainerEl = document.elementsFromPoint( x, y )
.find( function( el ){
return el.classList.contains( 'Q-circuit-board-container' )
}),
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( Q.Circuit.Editor.dragEl.circuitEl ){
Array.from( Q.Circuit.Editor.dragEl.children ).forEach( function( el ){
Q.Circuit.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 )
}
document.body.removeChild( Q.Circuit.Editor.dragEl )
Q.Circuit.Editor.dragEl = null
}
// If we have not dragged on to a circuit board
// then we’re throwing away this operation.
if( !boardContainerEl ){
if( Q.Circuit.Editor.dragEl.circuitEl ){
const
originCircuitEl = Q.Circuit.Editor.dragEl.circuitEl
originCircuit = originCircuitEl.circuit
originCircuit.history.createEntry$()
Array
.from( Q.Circuit.Editor.dragEl.children )
.forEach( function( child ){
originCircuit.clear$(
child.origin.momentIndex,
child.origin.registerIndex
)
})
Q.Circuit.Editor.onSelectionChanged( originCircuitEl )
Q.Circuit.Editor.onCircuitChanged( originCircuitEl )
}
// TIME TO DIE.
// Let’s keep a private reference to
// the current clipboard.
let clipboardToDestroy = Q.Circuit.Editor.dragEl
// Now we can remove our dragging reference.
Q.Circuit.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 = Q.Circuit.Editor.gridColumnToMomentIndex(
Q.Circuit.Editor.pointToGrid( droppedAtX )
),
droppedAtRegisterIndex = Q.Circuit.Editor.gridRowToRegisterIndex(
Q.Circuit.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( Q.Circuit.Editor.dragEl.circuitEl === circuitEl &&
Q.Circuit.Editor.dragEl.momentIndex === droppedAtMomentIndex &&
Q.Circuit.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( Q.Circuit.Editor.dragEl.children ),
draggedMomentDelta = droppedAtMomentIndex - Q.Circuit.Editor.dragEl.momentIndex,
draggedRegisterDelta = droppedAtRegisterIndex - Q.Circuit.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( Q.Circuit.Editor.dragEl.circuitEl ){
momentIndexTarget += childEl.origin.momentIndex - Q.Circuit.Editor.dragEl.momentIndex
registerIndexTarget += childEl.origin.registerIndex - Q.Circuit.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(
Q.Circuit.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( Q.Circuit.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 = Q.Circuit.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 ){
Q.Circuit.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 ){
//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( Q.Circuit.Editor.dragEl.circuitEl ){
registerIndexTarget += childEl.origin.registerIndex - Q.Circuit.Editor.dragEl.registerIndex + siblingDelta
}
return registerIndexTarget
})
// )
])
}
// 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( Q.Circuit.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 - Q.Circuit.Editor.dragEl.registerIndex
// Now put it where it wants to go,
// possibly overwriting a sibling component!
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 Q.isUsefulInteger( entry )
})
// Finally, we’re ready to set.
// circuit.set$(
setCommands.push([
childEl.getAttribute( 'gate-symbol' ),
momentIndexTarget,
fixedRegistersIndices
// )
])
}
else {
remainingComponents.forEach( function( componentEl, i ){
//circuit.set$(
setCommands.push([
+componentEl.getAttribute( 'register-indices-index' ) ?
gatesymbol :
Q.Gate.NOOP,
+componentEl.getAttribute( 'moment-index' ),
+componentEl.getAttribute( 'register-index' )
// )
])
})
// Finally, let’s separate and update
// all the components that were part of the drag.
foundComponents.forEach( function( componentEl ){
// circuit.set$(
setCommands.push([
+componentEl.getAttribute( 'register-indices-index' ) ?
gatesymbol :
Q.Gate.NOOP,
+componentEl.getAttribute( 'moment-index' ) + draggedMomentDelta,
+componentEl.getAttribute( 'register-index' ) + draggedRegisterDelta,
// )
])
})
}
// 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( Q.Circuit.Editor.dragEl.circuitEl ){
draggedOperations.forEach( function( childEl ){
Q.Circuit.Editor.dragEl.circuitEl.circuit.clear$(
childEl.origin.momentIndex,
childEl.origin.registerIndex
)
})
}
// And now set$ the operation
// in its new home.
// circuit.set$(
setCommands.push([
childEl.getAttribute( 'gate-symbol' ),
momentIndexTarget,
[ registerIndexTarget ]
// )
])
}
})
// DO IT DO IT DO IT
setCommands.forEach( function( setCommand ){
circuit.set$.apply( circuit, setCommand )
})
// Are we capable of making controls? Swaps?
Q.Circuit.Editor.onSelectionChanged( circuitEl )
Q.Circuit.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 ){
const originCircuitEl = Q.Circuit.Editor.dragEl.circuitEl
Q.Circuit.Editor.onSelectionChanged( originCircuitEl )
Q.Circuit.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( Q.Circuit.Editor.dragEl )
Q.Circuit.Editor.dragEl = null
}
///////////////////
// //
// Listeners //
// //
///////////////////
// These listeners must be applied
// 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 )