Source code:
Q-Gate.js
Gates to walk through
A quantum computer is just a collection of
qubits.
These qubits hold values like
1
, 0
, or some value inbetween.
A quantum computer
is able to calculate things
by changing the value of its qubits over time.
We tell the computer exactly how it should change
the value of a qubit
by instructing it to
“walk through” a series of
quantum gates.
As the qubit
“walks through” each gate
its value is changed based on the type of gate
it is passing through.
Contrary to what pop-science might tell you, there is nothing random or unpredictable about this process. Mathematically, a qubit is just a matrix. Similarly, a quantum gate is also just a matrix. To apply a gate to a qubit is to multiply these matrices together. To demonstrate this, let’s begin with a “Horizontal” qubit—commonly thought of as representing “off.” It has the following matrix form:
1 |
0 |
We would like to flip the value of this qubit from “off” to “on.” The result will be a “Vertical” qubit with the following matrix form:
0 |
1 |
In order to achieve this we must apply a Pauli X gate to our Horizontal qubit. Pauli X gates have the effect of “flipping” the value of a qubit. They are often thought of as the quantum equivalent of a classical NOT gate. Pauli X gates have the following matrix form:
0 | 1 |
1 | 0 |
We can now apply the Pauli X gate to the Horizontal qubit by multiplying their matrices together.
0 | 1 |
1 | 0 |
1 |
0 |
0 |
1 |
As anticipated, the resulting product matrix represents a Vertical qubit. (Warm fuzzies all around.)
Note how the gate’s matrix is the first factor and the qubit’s matrix is the second factor. The order matters because matrix multiplication is not commutative. With plain numbers the order of the factors does not change the product outcome. a × b = b × a. But with matrices the order does matter. [ a ] × [ b ] ≠ [ b ] × [ a ]. See matrix multiplication for an in-depth explanation.
Applying gates with code
Let’s look at three different ways
to apply a gate to a qubit
using Q’s API.
In this first example we will access
our gate’s matrix directly
and multiply it against our qubit—which is itself a matrix.
(Q.Qubit
extends Q.Matrix
and thereby inherits Q.Matrix
’s methods.
Meanwhile, Q.Gate
does not extend Q.Matrix
,
as it is sometimes desirable to create gates without matrices.
See Gates without matrices below for more details.)
Q.Gate.PAULI_X
.matrix
.multiply( Q.Qubit.HORIZONTAL )
.isEqualTo( Q.Qubit.VERTICAL )
true
Rather than access our gate’s matrix directly,
we can instead call the instance method
applyToQubit
.
Q.Gate.PAULI_X
.applyToQubit( Q.Qubit.HORIZONTAL )
.isEqualTo( Q.Qubit.VERTICAL )
true
We can also begin with the qubit rather than the gate.
Q.Qubit.HORIZONTAL
.applyGate( Q.Gate.PAULI_X )
.isEqualTo( Q.Qubit.VERTICAL )
true
Gates without matrices
In quantum computing theory a quantum gate is a matrix. But Q expands this notion of a “gate” to the more abstract notion of an “operation”—any function that is performed upon a qubit. This allows us to build custom “gates” that perform data visualizations, trigger interface changes, and so on.
The punchline
While applying gates to qubits in the above fashion is useful
for some debugging and
for building single-qubit state visualizations,
it has no practical use for
full circuit evaluation as
it cannot represent the state of
a multiple-qubit system, let alone
quantum entanglement.
For that we will need a proper
Circuit
class
for simulating multi-qubit states.
Constructor
Gate
Function( params: Object ) => Q.Gate
Expects a single object whose properties
will be applied directly to this gate instance.
No particular properties are required
and even the object argument itself is optional.
The properties not listed as “optional” below
are ones that will be assigned defaults
upon instantiation by the constructor.
Any remaining properties listed below would be undefined
unless supplied as arguments.
Here we will create a “gup” gate
that borrows its matrix
property
from the pre-existing PAULI_X
gate
constant.
This will enable our custom gate
to flip
qubits from a
horizontal state,
ie. “off” or 0,
to a vertical state,
ie. “on” or 1,
and back again—just like a
PAULI_X
gate.
var gup = new Q.Gate({
symbol: 'G',
name: 'Gup',
nameCss: 'gup',
matrix: Q.Gate.PAULI_X.matrix
})
We can now test that our new
gup
gate
flips qubit states between 0
and 1.
// Verify a “Horizontal” qubit has a value of |0⟩ // and a “Vertical” qubit has a value of |1⟩. Q.Qubit.HORIZONTAL.toStateVectorText()
"|0⟩"Q.Qubit.VERTICAL.toStateVectorText()
"|1⟩"// Now flip a “Horizontal” |0⟩ to a “Vertical” |1⟩ // and flip a “Vertical” |1⟩ to a “Horizontal” |0⟩. gup.applyToQubit( Q.Qubit.HORIZONTAL ) .toStateVectorText()
"|1⟩"gup.applyToQubit( Q.Qubit.VERTICAL ) .toStateVectorText()
"|0⟩"
We can similarly use a qubit’s
applyGate
method
which internally calls a gate’s
applyToQubit
method:
Q.Qubit.HORIZONTAL // Value begins at |0⟩. .applyGate( gup ) // Value is now |1⟩. .toStateVectorText()// Output that as text.
"|1⟩"Q.Qubit.VERTICAL // Value begins at |1⟩. .applyGate( gup ) // Value is now |0⟩. .toStateVectorText()// Output that as text.
"|0⟩"
We can also do this in one go:
Q.Qubit.HORIZONTAL // Value begins at |0⟩.
.applyGate( gup ) // Value is now |1⟩.
.applyGate( gup ) // Value is back to |0⟩.
.toStateVectorText()// Output that as text.
"|0⟩"
-
- symbol
String
One or two non-numeric characters used as a unique identifier among gates, and as a label in visual representations such as circuit diagrams. Will default to"?"
if undefined at creation. -
- symbolAmazonBraket
String
Identifier to be used for this gate when exporting circuit data as Amazon Braket code. If not supplied to the constructor,Gate
will define this value asthis.symbol.toLowerCase()
. -
- symbolSvg
String [optional]
A visual representation of this gate’s symbol in scalable vector graphic format. -
- name
String
The full name of this gate to be used as a title or long label in a visual representations, etc. Will default to"Unknown"
if undefined at creation. -
- nameCss
String
Identifier to be used in constructing a Cascading Style Sheet class name as defined inQ-Circuit-Editor.css
. Will default to"unknown"
if undefined at creation. -
- matrix
Q.Matrix [optional]
The matrix representation of this gate operation’s logic, if one exists. (All true quantum gates will have a matrix representation, but some visualization operations—such as the creation of a Bloch sphere—will not.) -
- applyToQubit
Function
An action to be performed upon an inputQubit
instance. If not supplied to theGate
constructor,Gate
will create an operation that multiplies the input qubit by the gate instance’smatrix
. If the gate instance’smatrix
is undefined,Gate
will create an operation that simply returns the input qubit untouched. While this is useful for inspection and debugging a single gate’s functionality, or building a visualization of a single qubit’s changing state, it has no practical use for circuit evaluation as it cannot represent the state of a multiple-qubit system, let alone entanglement. -
- index
Number
An auto-incrementing identification number assigned to the instance, used for minding the total number of instances created.
Static properties
-
- index
Number
The number of instances created so far. -
- findBy
Function( key: String, value: * ) ⇒ Q.Gate
Returns the first object withinQ.Gate.constants
to satisfy the constraintobject[ key ] === value
. -
- findBySymbol
Function( symbol: String ) ⇒ Q.Gate
Returns the result of calling the static methodfindBy
with"symbol"
as thekey
argument andsymbol
as the value argument. -
- findByName
Function( name: String ) ⇒ Q.Gate
Returns the result of calling the static methodfindBy
with"name"
as thekey
argument andname
as the value argument.
Constants and constant creation
-
- constants
Object
Constants are appended directly to theQ.Gate
object. For convenience they are also appended to thisQ.Gate
.constants object to make looking up constants in the JavaScript console trivial, and to make iterating across all constants convenient via functions likeObject.entries
,Object.keys
,Object.values
, and so on. The intention that a property act as a constant is signaled by its labelling in all-uppercase. -
- createConstant
Function( key: String, value: * )
Appends a property named bykey
with a value ofvalue
to both theGate
object and itsconstants
property. -
- createConstants
Function( … )
Expects an even number of arguments. Will use each pair in the sequence of arguments to callcreateConstant
.
Gates of small consequence
-
- IDENTITY
Q.Gate
An Identity gate has no effect on the value of the qubit it operates on; equivalent to multiplying a value by one. (Generally when a circuit is created from text or another source, any included identity gates are ignored. It is included here for completeness.)
Matrix representation as declared innew Q.Gate({ symbol: 'I', name: 'Identity', nameCss: 'identity', matrix: Q.Matrix.IDENTITY_2X2 })
Q.Matrix.IDENTITY_2X2
:1 0 0 1 -
- CURSOR
Q.Gate
Mathematically the “identity cursor” is equivalent to theIDENTITY
gate, however it is used by the visual circuit editor as a placeholder when a user is building controlled gates or swap gates (which operate on multiple qubits) from individual components. This is a novel Q invention.
Matrix representation as declared innew Q.Gate({ symbol: '*', name: 'Identity', nameCss: 'identity', matrix: Q.Matrix.IDENTITY_2X2 })
Q.Matrix.IDENTITY_2X2
:1 0 0 1
Standard single-qubit gates
-
- HADAMARD
Q.Gate
Applies a Hadamard transform to a single qubit. For the basis qubit states of 0 and 1 this has the effect of putting a qubit into superposition. It represents a rotation on the Bloch sphere around the Z-axis by π radians, followed by a rotation around the Y-axis by π÷2 radians.
Matrix representation:new Q.Gate({ symbol: 'H', name: 'Hadamard', nameCss: 'hadamard', matrix: Q.Matrix( [ Math.SQRT1_2, Math.SQRT1_2 ], [ Math.SQRT1_2, -Math.SQRT1_2 ] ) })
1 1 1 -1 1 √ 2 -
- PAULI_X
Q.Gate
The Pauli X gate represents a rotation on the Bloch sphere around the X-axis by π radians. It is the quantum equivalent of the classical NOT gate in that it maps 0 to 1 and 1 to 0.
Matrix representation:new Q.Gate({ symbol: 'X', name: 'Pauli X', nameCss: 'pauli-x', matrix: Q.Matrix( [ 0, 1 ], [ 1, 0 ] ) })
0 1 1 0 -
- PAULI_Y
Q.Gate
The Pauli Y gate represents a rotation on the Bloch sphere around the Y-axis by π radians. It maps 0 to i1 and 1 to -i0.
Matrix representation:new Q.Gate({ symbol: 'Y', name: 'Pauli Y', nameCss: 'pauli-y', matrix: Q.Matrix( [ 0, new Q.ComplexNumber( 0, -1 )], [ new Q.ComplexNumber( 0, 1 ), 0 ] ) })
0 -i i 0 -
- PAULI_Z
Q.Gate
The Pauli Z gate represents a rotation on the Bloch sphere around the Z-axis by π radians. It is a special case of a Phase shift gate where ϕ = π, and is therefore sometimes referred to as a “phase-flip” gate. It leaves the basis state 0 unchanged and maps 1 to -1.
Matrix representation:new Q.Gate({ symbol: 'Z', name: 'Pauli Z', nameCss: 'pauli-z', matrix: Q.Matrix( [ 1, 0 ], [ 0, -1 ] ) })
1 0 0 -1 -
- PHASE
Q.Gate
Phase gates are a family of quantum gates that employ the variable ϕ (phi) to represent tracing a horizontal arc (a line of latitude) of ϕ radians around the Bloch sphere. They leave the basis state 0 unchanged and map 1 to eiϕ 1. The probability of measuring a 0 or 1 is unchanged after applying a phase shift, however it modifies the phase of the quantum state. This particular form used for ourPHASE
constant represents a rotation on the Bloch sphere around the Z-axis of π ÷ 2 radians.
Note that there is no explicitly definednew Q.Gate({ symbol: 'P', name: 'Phase', nameCss: 'phase', updateMatrix$: function( phi ){ if( Q.isUsefulNumber( phi ) === true ) this.phi = phi this.matrix = new Q.Matrix( [ 1, 0 ], [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, this.phi ))] return this }, applyToQubit: function( qubit, phi ){ if( Q.isUsefulNumber( phi ) !== true ) phi = this.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 )) } })
matrix
property. It will be created upon instantiation when the constructor calls the argument object’supdateMatrix$
function. Matrix representation (when ϕ = 1):1 0 0 ei
Using varying values for ϕ is easy. We can clone this PHASE constant, provide it a newsymbol
(so it has a unique identifier), and a new value for ϕ. In this example we create a gate calledfox
with a ϕ value of π.
Our value of ϕ is now set equal to π and we can apply this gate to any qubit.var fox = Q.Gate.PHASE.clone({ symbol: 'F', phi: Math.PI })
But we can also send a temporary value for ϕ that will be used only during this function execution; it will not changefox.applyToQubit( Q.Qubit.DIAGONAL ).toTsv()
" 0.707 -0.707"fox
’s set value of ϕ. Let’s try temporarily setting ϕ to π ÷ 4.
Rather than begin our expression with a gate, we can instead begin with a qubit, and then apply ourfox.applyToQubit( Q.Qubit.DIAGONAL, Math.PI / 4 ).toTsv()
" 0.707 0.5 + 0.5i"fox
gate to it.
But what’s more clever is that additional arguments will be passed directly to the gate’sQ.Qubit.DIAGONAL .applyGate( fox ) .toTsv()
" 0.707 -0.707"applyToQubit
method, so we can still use temporary values for ϕ.Q.Qubit.DIAGONAL .applyGate( fox, Math.PI / 4 ) .toTsv()
" 0.707 0.5 + 0.5i" -
- PI_8
Q.Gate
The π ÷ 8 gate is a special case of a Phase shift gate where the ϕ (phi) variable is set to π ÷ 4. (But why is it called “π ÷ 8” when it actually divides π by 4?) Like all phase shift gates, it represents a rotation on the Bloch sphere around the Z-axis.
Matrix representation:new Q.Gate({ symbol: 'T', name: 'π ÷ 8', nameCss: 't', matrix: Q.Matrix( [ 1, 0 ], [ 0, Q.ComplexNumber.E.power( new Q.ComplexNumber( 0, Math.PI / 4 )) ] ) })
1 0 0 eiπ ÷ 4
Multi-qubit gates
-
- SWAP
Q.Gate
The Swap gate swaps the value of two qubits. It is defined here with respect to the bases 00, 01, 10, and 11.
Matrix representation:new Q.Gate({ symbol: 'S', name: 'Swap', nameCss: 'swap', matrix: Q.Matrix( [ 1, 0, 0, 0 ], [ 0, 0, 1, 0 ], [ 0, 1, 0, 0 ], [ 0, 0, 0, 1 ]) ) })
1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 -
- SWAP1_2
Q.Gate
The √Swap gate performs half of a swap between two qubits. It is not maximally entangling. More than one application of it is required to produce a Bell state from its product states. It is defined here with respect to the bases 00, 01, 10, and 11.
Matrix representation:new Q.Gate({ symbol: '√', name: '√Swap', nameCss: 'swap1-2', matrix: Q.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, 0, 0, 1 ]) ) })
1 0 0 0 0 1 2 1 2 0 0 1 2 1 2 0 0 0 0 1
Implicit gates
Q.Circuit
supports several multi-qubit gates implictly
through composition,
rather than explicitly with Gate
constants.
For example,
Q does not explictly define a
Controlled-Not (CNOT) gate,
Controlled Swap (Fredkin) gate,
Toffolli (CCNOT) gate,
and so on
because they are all easily composed
by adding a control matrix
to any existing gate matrix.
Prototype properties
-
- clone
Function ⇒ Q.Gate
Returns a newGate
instance with properties cloned from this instance. -
- applyToQubits
Function( Q.Qubit, … ) ⇒ Array
Calls the instance methodapplyToQubit
for each suppliedQ.Qubit
argument and returns an Array of results. For example:
The above will return:Q.Gate.PAULI_X.applyToQubits( Q.Qubit.HORIZONTAL, Q.Qubit.VERTICAL, … )
[ Q.Qubit.VERTICAL, Q.Qubit.HORIZONTAL, … ]
Or for even more fun with Arrays of qubits:
While this is useful for inspection and debugging a single gate’s functionality, or building a visualization of a single qubit’s changing state, it has no practical use for circuit evaluation as it cannot represent the state of a multiple-qubit system, let alone entanglement. Will not return aQ.Gate.IDENTITY .applyToQubits( ...Object.values( Q.Qubit.constants ) ) .map( Q.Qubit.toText )
[ " 1↵0", // Horizontal " 0↵1", // Vertical " 0.707↵ 0.707", // Diagonal " 0.707↵-0.707", // Anti-Diagonal " 0.707↵-0.707i",// Right-hand Circular Polarized " 0.707↵ 0.707i" // Left-hand Circular Polarized ]Q.Gate
instance, and therefore halts “Fluent interface” method chaining along this prototype. -
- set$
Function( key: String, value: * ) ⇒ Q.Gate
Sets a property on this instance with a key ofkey
and a value ofvalue
, then returns the instance.