Skip to content

Commit

Permalink
Simplifying the implementation of the single qubit operations.
Browse files Browse the repository at this point in the history
Not sure if it really simplifies them, but they are now expressed as as matrix operators.

Also: increase the number of times we run the QFT spec that has a random chance of getting the wrong answer -- I just saw it fail.
  • Loading branch information
davidbkemp committed Feb 25, 2018
1 parent 72ea4b4 commit 4c8438b
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 177 deletions.
1 change: 1 addition & 0 deletions lib/Complex.js
Expand Up @@ -96,6 +96,7 @@ export default class Complex {
return Math.abs(this.real - other.real) < 0.0001 && Math.abs(this.imaginary - other.imaginary) < 0.0001
}

static ONE = new Complex(1, 0);
static ZERO = new Complex(0, 0);
static SQRT2 = new Complex(Math.SQRT2, 0);
static SQRT1_2 = new Complex(Math.SQRT1_2, 0);
Expand Down
203 changes: 119 additions & 84 deletions lib/QState.js
Expand Up @@ -16,6 +16,19 @@ function sparseAssign(array, index, value) {
}
}

/*
Add amplitude to the existing amplitude for state in the amplitudes object
Keep the object sparse by deleting values close to zero.
*/
function sparseAdd(amplitudes, state, amplitude) {
const newAmplitude = (amplitudes[state] || Complex.ZERO).add(amplitude);
if (newAmplitude.magnitude() > Constants.roundToZero) {
amplitudes[state] = newAmplitude
} else {
delete amplitudes[state]
}
}

function convertBitQualifierToBitRange(bits, numBits) {
if (bits == null) {
throw new Error('bit qualification must be supplied');
Expand Down Expand Up @@ -104,6 +117,36 @@ function createBitMask(bits) {
return mask;
}

const hadamardMatrix = [
[Complex.SQRT1_2, Complex.SQRT1_2],
[Complex.SQRT1_2, Complex.SQRT1_2.negate()]
];

const xMatrix = [
[Complex.ZERO, Complex.ONE],
[Complex.ONE, Complex.ZERO]
];

const yMatrix = [
[Complex.ZERO, new Complex(0, -1)],
[new Complex(0, 1), Complex.ZERO]
];

const zMatrix = [
[Complex.ONE, Complex.ZERO],
[Complex.ZERO, Complex.ONE.negate()]
];

const sMatrix = [
[Complex.ONE, Complex.ZERO],
[Complex.ZERO, new Complex(0, 1)]
];

const tMatrix = [
[Complex.ONE, Complex.ZERO],
[Complex.ZERO, new Complex(Math.SQRT1_2, Math.SQRT1_2)]
];

export default class QState {
constructor(numBits, amplitudes) {
validateArgs(arguments, 1, 'new QState() must be supplied with number of bits (optionally with amplitudes as well)');
Expand Down Expand Up @@ -181,14 +224,17 @@ export default class QState {

kron = this.tensorProduct;

static applyOperatorMatrix(matrix, bitValue, amplitude) {
return [
matrix[0][bitValue].multiply(amplitude),
matrix[1][bitValue].multiply(amplitude)
]
}

controlledHadamard = (function () {
return function (controlBits, targetBits) {
validateArgs(arguments, 2, 2, 'Must supply control and target bits to controlledHadamard()');
return this.controlledApplicatinOfqBitOperator(controlBits, targetBits, (amplitudeOf0, amplitudeOf1) => {
const newAmplitudeOf0 = amplitudeOf0.add(amplitudeOf1).multiply(Complex.SQRT1_2);
const newAmplitudeOf1 = amplitudeOf0.subtract(amplitudeOf1).multiply(Complex.SQRT1_2);
return {amplitudeOf0: newAmplitudeOf0, amplitudeOf1: newAmplitudeOf1};
});
return this.controlledApplicationOfqBitOperator(controlBits, targetBits, hadamardMatrix);
};
}());

Expand All @@ -199,14 +245,16 @@ export default class QState {

controlledXRotation(controlBits, targetBits, angle) {
validateArgs(arguments, 3, 3, 'Must supply control bits, target bits, and an angle, to controlledXRotation()');
return this.controlledApplicatinOfqBitOperator(controlBits, targetBits, (amplitudeOf0, amplitudeOf1) => {
const halfAngle = angle / 2;
const cos = new Complex(Math.cos(halfAngle));
const negative_i_sin = new Complex(0, -Math.sin(halfAngle));
const newAmplitudeOf0 = amplitudeOf0.multiply(cos).add(amplitudeOf1.multiply(negative_i_sin));
const newAmplitudeOf1 = amplitudeOf0.multiply(negative_i_sin).add(amplitudeOf1.multiply(cos));
return {amplitudeOf0: newAmplitudeOf0, amplitudeOf1: newAmplitudeOf1};
});
const halfAngle = angle / 2;
const cosine = new Complex(Math.cos(halfAngle));
const negativeISine = new Complex(0, -Math.sin(halfAngle));

const rotationMatrix = [
[cosine, negativeISine],
[negativeISine, cosine]
];

return this.controlledApplicationOfqBitOperator(controlBits, targetBits, rotationMatrix);
}

rotateX(targetBits, angle) {
Expand All @@ -216,14 +264,15 @@ export default class QState {

controlledYRotation(controlBits, targetBits, angle) {
validateArgs(arguments, 3, 3, 'Must supply control bits, target bits, and an angle, to controlledYRotation()');
return this.controlledApplicatinOfqBitOperator(controlBits, targetBits, (amplitudeOf0, amplitudeOf1) => {
const halfAngle = angle / 2;
const cos = new Complex(Math.cos(halfAngle));
const sin = new Complex(Math.sin(halfAngle));
const newAmplitudeOf0 = amplitudeOf0.multiply(cos).add(amplitudeOf1.multiply(sin.negate()));
const newAmplitudeOf1 = amplitudeOf0.multiply(sin).add(amplitudeOf1.multiply(cos));
return {amplitudeOf0: newAmplitudeOf0, amplitudeOf1: newAmplitudeOf1};
});
const halfAngle = angle / 2;
const cosine = new Complex(Math.cos(halfAngle));
const sine = new Complex(Math.sin(halfAngle));
const rotationMatrix = [
[cosine, sine.negate()],
[sine, cosine]
];

return this.controlledApplicationOfqBitOperator(controlBits, targetBits, rotationMatrix);
}


Expand All @@ -234,14 +283,14 @@ export default class QState {

controlledZRotation(controlBits, targetBits, angle) {
validateArgs(arguments, 3, 3, 'Must supply control bits, target bits, and an angle to controlledZRotation()');
return this.controlledApplicatinOfqBitOperator(controlBits, targetBits, (amplitudeOf0, amplitudeOf1) => {
const halfAngle = angle / 2;
const cos = new Complex(Math.cos(halfAngle));
const isin = new Complex(0, Math.sin(halfAngle));
const newAmplitudeOf0 = amplitudeOf0.multiply(cos.subtract(isin));
const newAmplitudeOf1 = amplitudeOf1.multiply(cos.add(isin));
return {amplitudeOf0: newAmplitudeOf0, amplitudeOf1: newAmplitudeOf1};
});
const halfAngle = angle / 2;
const cosine = new Complex(Math.cos(halfAngle));
const iSine = new Complex(0, Math.sin(halfAngle));
const rotationMatrix = [
[cosine.subtract(iSine), Complex.ZERO],
[Complex.ZERO, cosine.add(iSine)]
];
return this.controlledApplicationOfqBitOperator(controlBits, targetBits, rotationMatrix);
}

rotateZ(targetBits, angle) {
Expand All @@ -251,13 +300,13 @@ export default class QState {

controlledR(controlBits, targetBits, angle) {
validateArgs(arguments, 3, 3, 'Must supply control and target bits, and an angle to controlledR().');
return this.controlledApplicatinOfqBitOperator(controlBits, targetBits, (amplitudeOf0, amplitudeOf1) => {
const cos = new Complex(Math.cos(angle));
const isin = new Complex(0, Math.sin(angle));
const newAmplitudeOf0 = amplitudeOf0;
const newAmplitudeOf1 = amplitudeOf1.multiply(cos.add(isin));
return {amplitudeOf0: newAmplitudeOf0, amplitudeOf1: newAmplitudeOf1};
});
const cosine = new Complex(Math.cos(angle));
const iSine = new Complex(0, Math.sin(angle));
const rotationMatrix = [
[Complex.ONE, Complex.ZERO],
[Complex.ZERO, cosine.add(iSine)]
];
return this.controlledApplicationOfqBitOperator(controlBits, targetBits, rotationMatrix);
}

r(targetBits, angle) {
Expand All @@ -270,9 +319,7 @@ export default class QState {

controlledX(controlBits, targetBits) {
validateArgs(arguments, 2, 2, 'Must supply control and target bits to cnot() / controlledX().');
return this.controlledApplicatinOfqBitOperator(controlBits, targetBits, (amplitudeOf0, amplitudeOf1) => {
return {amplitudeOf0: amplitudeOf1, amplitudeOf1: amplitudeOf0};
});
return this.controlledApplicationOfqBitOperator(controlBits, targetBits, xMatrix);
}

cnot = this.controlledX;
Expand All @@ -287,9 +334,7 @@ export default class QState {

controlledY(controlBits, targetBits) {
validateArgs(arguments, 2, 2, 'Must supply control and target bits to controlledY().');
return this.controlledApplicatinOfqBitOperator(controlBits, targetBits, (amplitudeOf0, amplitudeOf1) => {
return {amplitudeOf0: amplitudeOf1.multiply(new Complex(0, -1)), amplitudeOf1: amplitudeOf0.multiply(new Complex(0, 1))};
});
return this.controlledApplicationOfqBitOperator(controlBits, targetBits, yMatrix);
}

y(targetBits) {
Expand All @@ -301,9 +346,7 @@ export default class QState {

controlledZ(controlBits, targetBits) {
validateArgs(arguments, 2, 2, 'Must supply control and target bits to controlledZ().');
return this.controlledApplicatinOfqBitOperator(controlBits, targetBits, (amplitudeOf0, amplitudeOf1) => {
return {amplitudeOf0, amplitudeOf1: amplitudeOf1.negate()};
});
return this.controlledApplicationOfqBitOperator(controlBits, targetBits, zMatrix);
}

z(targetBits) {
Expand All @@ -316,9 +359,7 @@ export default class QState {
controlledS(controlBits, targetBits) {
// Note this could actually be implemented as controlledR(controlBits, targetBits, PI/2)
validateArgs(arguments, 2, 2, 'Must supply control and target bits to controlledS().');
return this.controlledApplicatinOfqBitOperator(controlBits, targetBits, (amplitudeOf0, amplitudeOf1) => {
return {amplitudeOf0, amplitudeOf1: amplitudeOf1.multiply(new Complex(0, 1))};
});
return this.controlledApplicationOfqBitOperator(controlBits, targetBits, sMatrix);
}

s(targetBits) {
Expand All @@ -328,16 +369,11 @@ export default class QState {

S = this.s;

controlledT = (function () {
controlledT(controlBits, targetBits) {
// Note this could actually be implemented as controlledR(controlBits, targetBits, PI/4)
const expPiOn4 = new Complex(Math.SQRT1_2, Math.SQRT1_2);
return function (controlBits, targetBits) {
validateArgs(arguments, 2, 2, 'Must supply control and target bits to controlledT().');
return this.controlledApplicatinOfqBitOperator(controlBits, targetBits, (amplitudeOf0, amplitudeOf1) => {
return {amplitudeOf0, amplitudeOf1: amplitudeOf1.multiply(expPiOn4)};
});
};
}());
validateArgs(arguments, 2, 2, 'Must supply control and target bits to controlledT().');
return this.controlledApplicationOfqBitOperator(controlBits, targetBits, tMatrix);
}

t(targetBits) {
validateArgs(arguments, 1, 1, 'Must supply target bits to t().');
Expand Down Expand Up @@ -387,33 +423,32 @@ export default class QState {
return this.controlledX(controlBits, targetBit);
}

controlledApplicatinOfqBitOperator = (function () {
function applyToOneBit(controlBits, targetBit, qbitFunction, qState) {
const newAmplitudes = {};
const statesThatCanBeSkipped = {};
const targetBitMask = 1 << targetBit;
const controlBitMask = createBitMask(controlBits);
qState.each((stateWithAmplitude) => {
const state = stateWithAmplitude.asNumber();
if (statesThatCanBeSkipped[stateWithAmplitude.index]) return;
statesThatCanBeSkipped[state ^ targetBitMask] = true;
const indexOf1 = state | targetBitMask;
const indexOf0 = indexOf1 - targetBitMask;
if (controlBits == null || ((state & controlBitMask) === controlBitMask)) {
const result = qbitFunction(qState.amplitude(indexOf0), qState.amplitude(indexOf1));
sparseAssign(newAmplitudes, indexOf0, result.amplitudeOf0);
sparseAssign(newAmplitudes, indexOf1, result.amplitudeOf1);
} else {
sparseAssign(newAmplitudes, indexOf0, qState.amplitude(indexOf0));
sparseAssign(newAmplitudes, indexOf1, qState.amplitude(indexOf1));
}
});
static applyToOneBit(controlBits, targetBit, operatorMatrix, qState) {
const newAmplitudes = {};
const targetBitMask = 1 << targetBit;
const inverseTargetBitMask = ~targetBitMask;
const controlBitMask = createBitMask(controlBits);

return new QState(qState.numBits(), newAmplitudes);
}
qState.each((stateWithAmplitude) => {
const state = stateWithAmplitude.asNumber();
if (controlBits == null || ((state & controlBitMask) === controlBitMask)) {
const bitValue = ((targetBitMask & state) > 0) ? 1 : 0;
const result = QState.applyOperatorMatrix(operatorMatrix, bitValue, stateWithAmplitude.amplitude);
const zeroState = state & inverseTargetBitMask;
const oneState = state | targetBitMask;
sparseAdd(newAmplitudes, zeroState, result[0]);
sparseAdd(newAmplitudes, oneState, result[1]);
} else {
newAmplitudes[state] = stateWithAmplitude.amplitude
}
});

return new QState(qState.numBits(), newAmplitudes);
}

return function (controlBits, targetBits, qbitFunction) {
validateArgs(arguments, 3, 3, 'Must supply control bits, target bits, and qbitFunction to controlledApplicatinOfqBitOperator().');
controlledApplicationOfqBitOperator = (function () {
return function (controlBits, targetBits, operatorMatrix) {
validateArgs(arguments, 3, 3, 'Must supply control bits, target bits, and qbitFunction to controlledApplicationOfqBitOperator().');
const targetBitArray = convertBitQualifierToBitArray(targetBits, this.numBits());
let controlBitArray = null;
if (controlBits != null) {
Expand All @@ -423,10 +458,10 @@ export default class QState {
let result = this;
for (let i = 0; i < targetBitArray.length; i++) {
const targetBit = targetBitArray[i];
result = applyToOneBit(controlBitArray, targetBit, qbitFunction, result);
result = QState.applyToOneBit(controlBitArray, targetBit, operatorMatrix, result);
}
return result;
};
}
}());

applyFunction = (function () {
Expand Down
4 changes: 2 additions & 2 deletions spec/qft.spec.js
Expand Up @@ -59,8 +59,8 @@ describe('QState.qft (Quantum Fourier Transform)', () => {
const inputBits = {from: 2, to: 4};
const outBits = {from: 0, to: 1};
let gcd = 0;
// Do this 10 times since it is random :-)
for (let i = 0; i < 10; i++) {
// Do this 100 times since it is random and sometimes takes a long time :-)
for (let i = 0; i < 100; i++) {
let qstate = Q('|00000>').hadamard(inputBits);
qstate = qstate.applyFunction(inputBits, outBits, (x) => { return x % 4 });
const result = qstate.qft(inputBits).measure(inputBits).result;
Expand Down

0 comments on commit 4c8438b

Please sign in to comment.