Home Reference Source # src/ops/basics.js

``````/*
* Copyright (c) 2018 Isaac Phoenix ([email protected]).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
Defines the BasicGate class, the base class of all gates, the
BasicRotationGate class, the SelfInverseGate, the FastForwardingGate, the
ClassicalInstruction gate, and the BasicMathGate class.

Gates overload the | operator to allow the following syntax:

@example
Gate | (qureg1, qureg2, qureg2)
Gate | (qureg, qubit)
Gate | qureg
Gate | qubit
Gate | (qubit,)

This means that for more than one quantum argument (right side of | ), a tuple
needs to be made explicitely, while for one argument it is optional.
*/
import math from 'mathjs'
import { BasicQubit } from '../types/qubit'
import Command from './command'
import {arrayIsTuple, ObjectCopy} from '../libs/util'
import {NotMergeable} from '../meta/error'

export const ANGLE_PRECISION = 12
export const ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION

/**
* @abstract
* @class BasicGate
* @desc Base class of all gates.
*/
export class BasicGate {
/**
* @constructor
Note:
Set interchangeable qubit indices!
(gate.interchangeable_qubit_indices)

As an example, consider

@example
ExampleGate | (a,b,c,d,e)

where a and b are interchangeable. Then, call this function as
follows:

@example
this.set_interchangeable_qubit_indices([[0,1]])

As another example, consider

@example
ExampleGate2 | (a,b,c,d,e)

where a and b are interchangeable and, in addition, c, d, and e
are interchangeable among themselves. Then, call this function as

@example
this.set_interchangeable_qubit_indices([[0,1],[2,3,4]])
*/
constructor() {
this.interchangeableQubitIndices = []
}

/**
* @throws {Error}
*/
getInverse() {
throw new Error('BasicGate: No getInverse() implemented.')
}

/**
* @throws {NotMergeable}
*/
getMerged() {
throw new NotMergeable('BasicGate: No getMerged() implemented.')
}

/**
* @throws {Error}
*/
toString() {
throw new Error('BasicGate: No toString() implemented.')
}

/**
* @return {string}
*/
inspect() {
return this.toString()
}

/**
Convert quantum input of "gate | quantum input" to internal formatting.

A Command object only accepts tuples of Quregs (list of Qubit objects)
as qubits input parameter. However, with this function we allow the
user to use a more flexible syntax:

1) Gate | qubit
2) Gate | [qubit0, qubit1]
3) Gate | qureg
4) Gate | (qubit, )
5) Gate | (qureg, qubit)

where qubit is a Qubit object and qureg is a Qureg object. This
function takes the right hand side of | and transforms it to the
correct input parameter of a Command object which is:

1) -> Gate | ([qubit], )
2) -> Gate | ([qubit0, qubit1], )
3) -> Gate | (qureg, )
4) -> Gate | ([qubit], )
5) -> Gate | (qureg, [qubit])

@param {Qubit|Qubit[]|Qureg|Qureg[]} qubits a Qubit object, a list of Qubit objects, a Qureg object,
or a tuple of Qubit or Qureg objects (can be mixed).
@returns {Qureg[]} Canonical representation A tuple containing Qureg (or list of Qubits) objects.
*/
static makeTupleOfQureg(qubits) {
const isTuple = arrayIsTuple(qubits)
if (!isTuple) {
qubits = [qubits]
}
qubits.forEach((looper, idx) => {
if (looper instanceof BasicQubit) {
qubits[idx] = [looper]
}
})
return qubits.slice(0)
}

/**
Helper function to generate a command consisting of the gate and the qubits being acted upon.

@param qubits {Qubit | Array.<Qubit> | Qureg} see BasicGate.makeTupleOfQureg(qubits)
@return {Command} A Command object containing the gate and the qubits.
*/
generateCommand(qubits) {
const qs = BasicGate.makeTupleOfQureg(qubits)
const engines = []
qs.forEach((reg) => {
reg.forEach(q => engines.push(q.engine))
})
const eng = engines
return new Command(eng, this, qs)
}

/**
Operator| overload which enables the syntax Gate | qubits.

@example
1) Gate | qubit
2) Gate | [qubit0, qubit1]
3) Gate | qureg
4) Gate | (qubit, )
5) Gate | (qureg, qubit)

@param qubits {Qubit | Array.<Qubit> | Qureg}
a Qubit object, a list of Qubit objects, a Qureg object,
or a tuple of Qubit or Qureg objects (can be mixed).
*/
or(qubits) {
const cmd = this.generateCommand(qubits)
cmd.apply()
}

/**
* @param {BasicGate | Object} other
* @return {boolean}
*/
equal(other) {
return this.__proto__ === other.__proto__
}

/**
* @return {BasicGate}
*/
copy() {
return ObjectCopy(this)
}
}

/**
* @class SelfInverseGate
* @desc Self-inverse basic gate class.
* Automatic implementation of the getInverse-member function for self-inverse gates.
* @example
// getInverse(H) == H, it is a self-inverse gate:
getInverse(H) | qubit
*/
export class SelfInverseGate extends BasicGate {
getInverse() {
return ObjectCopy(this)
}
}

/**
* @class BasicRotationGate
* @desc
Defines a base class of a rotation gate.

A rotation gate has a continuous parameter (the angle), labeled 'angle' /
this.angle. Its inverse is the same gate with the negated argument.
Rotation gates of the same class can be merged by adding the angles.
The continuous parameter is modulo 4 * pi, this.angle is in the interval
[0, 4 * pi).
*/
export class BasicRotationGate extends BasicGate {
/**
* @constructor
Initialize a basic rotation gate.
@param angle {number} Angle of rotation (saved modulo 4 * pi)
*/
constructor(angle, ...args) {
super(...args)

let rounded_angle = math.round(math.mod(angle, 4.0 * Math.PI), ANGLE_PRECISION)
if (rounded_angle > 4 * Math.PI - ANGLE_TOLERANCE) {
rounded_angle = 0.0
}
this.angle = rounded_angle
}

/**
* @return {BasicRotationGate}
Return the inverse of this rotation gate (negate the angle, return new
object).
*/
getInverse() {
if (this.angle == 0) {
return new this.__proto__.constructor(0)
} else {
return new this.__proto__.constructor(-this.angle + 4 * Math.PI)
}
}

/**
Return self merged with another gate.

Default implementation handles rotation gate of the same type, where
angles are simply added.

@param {BasicRotationGate|Object} other
@throws {NotMergeable}  For non-rotation gates or rotation gates of different type.
@return {BasicRotationGate} New object representing the merged gates.
*/
getMerged(other) {
if (other instanceof BasicRotationGate) {
return new this.__proto__.constructor(this.angle + other.angle)
}
throw new NotMergeable('Can\'t merge different types of rotation gates.')
}

toString() {
return `\${this.constructor.name}(\${this.angle})`
}

/**
Return the Latex string representation of a BasicRotationGate.

Returns the class name and the angle as a subscript, i.e.

@example
[CLASSNAME]\$_[ANGLE]\$
@return {string}
*/
texString() {
return `\${this.constructor.name}\$_{\${this.angle}}\$`
}

equal(other) {
if (other instanceof BasicRotationGate) {
return this.angle == other.angle
}
return false
}
}

/**
* @class BasicPhaseGate
* @desc
Defines a base class of a phase gate.

A phase gate has a continuous parameter (the angle), labeled 'angle' /
this.angle. Its inverse is the same gate with the negated argument.
Phase gates of the same class can be merged by adding the angles.
The continuous parameter is modulo 2 * pi, this.angle is in the interval
[0, 2 * pi).
*/
export class BasicPhaseGate extends BasicGate {
/**
Initialize a basic rotation gate.

@param {number} angle Angle of rotation (saved modulo 2 * pi)
*/
constructor(angle, ...args) {
super(...args)
let rounded_angle = math.round(math.mod(angle, 2.0 * Math.PI), ANGLE_PRECISION)
if (rounded_angle > 2 * Math.PI - ANGLE_TOLERANCE) {
rounded_angle = 0.0
}
this.angle = rounded_angle
}

/**
Return the inverse of this rotation gate (negate the angle, return new object).
@return {BasicPhaseGate}
*/
getInverse() {
if (this.angle == 0) {
return new this.__proto__.constructor(0)
} else {
return new this.__proto__.constructor(-this.angle + 2 * Math.PI)
}
}

/**
Return self merged with another gate.

Default implementation handles rotation gate of the same type, where angles are simply added.

@param {BasicPhaseGate} other Phase gate of same type.
@throws NotMergeable For non-rotation gates or rotation gates of different type.
@return {BasicPhaseGate} New object representing the merged gates.
*/
getMerged(other) {
if (other instanceof BasicPhaseGate) {
return new this.__proto__.constructor(this.angle + other.angle)
}
throw new NotMergeable('Can\'t merge different types of rotation gates.')
}

toString() {
return `\${this.constructor.name}(\${this.angle})`
}

texString() {
return `\${this.constructor.name}\$_{\${this.angle}}\$`
}

equal(other) {
if (other instanceof BasicPhaseGate) {
return this.angle === other.angle
}
return false
}
}

/**
* @class ClassicalInstructionGate
* @desc
Classical instruction gates never have control qubits.
Base class for all gates which are not quantum gates in the typical sense,
e.g., measurement, allocation/deallocation, ...
*/
export class ClassicalInstructionGate extends BasicGate {

}

/**
* @class FastForwardingGate
* @desc
Base class for classical instruction gates which require a fast-forward
through compiler engines that cache / buffer gates. Examples include
Measure and Deallocate, which both should be executed asap, such
that Measurement results are available and resources are freed,
respectively.

Note:
The only requirement is that FlushGate commands run the entire
circuit. FastForwardingGate objects can be used but the user cannot
expect a measurement result to be available for all back-ends when
calling only Measure. E.g., for the IBM Quantum Experience back-end,
sending the circuit for each Measure-gate would be too inefficient,
which is why a final

@example

eng.flush()

is required before the circuit gets sent through the API.
*/
export class FastForwardingGate extends ClassicalInstructionGate {

}

/**
* @class BasicMathGate
* @desc
Base class for all math gates.

It allows efficient emulation by providing a mathematical representation
which is given by the concrete gate which derives from this base class.
The AddConstant gate, for example, registers a function of the form

@example

return (x+a,)

upon initialization. More generally, the function takes integers as
parameters and returns a tuple / list of outputs, each entry corresponding
to the function input. As an example, consider out-of-place
multiplication, which takes two input registers and adds the result into a
third, i.e., (a,b,c) -> (a,b,c+a*b). The corresponding function then is

@example

function multiply(a,b,c)
return (a,b,c+a*b)
*/
export class BasicMathGate extends BasicGate {
/**
* @constructor
Initialize a BasicMathGate by providing the mathematical function that it implements.

@param {function} mathFunc Function which takes as many int values as
input, as the gate takes registers. For each of these values,
it then returns the output (i.e., it returns a list/tuple of
output values).

@example

return (a,a+b)

If the gate acts on, e.g., fixed point numbers, the number of bits per
register is also required in order to describe the action of such a
mathematical gate. For this reason, there is

@example

BasicMathGate.get_math_function(qubits)

which can be overwritten by the gate deriving from BasicMathGate.

@example

function get_math_function(self, qubits)
n = len(qubits)
scal = 2.**n
function math_fun(a)
return (int(scal * (math.sin(math.pi * a / scal))),)
return math_fun
*/
constructor(mathFunc, ...args) {
super(...args)
this.mathFunc = x => Array.from(mathFunc(...x))
}

/**
Return the math function which corresponds to the action of this math
gate, given the input to the gate (a tuple of quantum registers).

@param {Array.<Qureg>} qubits Qubits to which the math gate is being applied.

@return {function} javascript function describing the action of this
gate. (See BasicMathGate.constructor for an example).
*/
getMathFunction(qubits) {
return this.mathFunc
}

toString() {
return 'MATH'
}
}
``````