src/ops/basics.js
/*
* Copyright (c) 2018 Isaac Phoenix (tearsofphoenix@icloud.com).
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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'
const ANGLE_PRECISION = 12
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[0]
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
function add(x)
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
function add(a,b)
return (a,a+b)
BasicMathGate.__init__(self, add)
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[0])
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'
}
}