diff --git a/library/signed/README.md b/library/signed/README.md new file mode 100644 index 0000000000..3d8a844083 --- /dev/null +++ b/library/signed/README.md @@ -0,0 +1,2 @@ +# Signed +The `signed` library defines signed quantum integer primitives and operations. \ No newline at end of file diff --git a/library/signed/qsharp.json b/library/signed/qsharp.json new file mode 100644 index 0000000000..3e21747f14 --- /dev/null +++ b/library/signed/qsharp.json @@ -0,0 +1,21 @@ +{ + "author": "Microsoft", + "license": "MIT", + "dependencies": { + "Unstable": { + "github": { + "owner": "Microsoft", + "repo": "qsharp", + "ref": "d1fb2a1", + "path": "library/unstable" + } + } + }, + "files": [ + "src/Comparison.qs", + "src/Measurement.qs", + "src/Operations.qs", + "src/Tests.qs", + "src/Utils.qs" + ] +} diff --git a/library/signed/src/Comparison.qs b/library/signed/src/Comparison.qs new file mode 100644 index 0000000000..e283fab1b3 --- /dev/null +++ b/library/signed/src/Comparison.qs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import Std.Arrays.Tail, Std.Arrays.Zipped, Std.Arrays.Most, Std.Arrays.Rest; +import Std.Diagnostics.Fact; +import Unstable.Arithmetic.ApplyIfGreaterLE; + +/// # Summary +/// Wrapper for signed integer comparison: `result = xs > ys`. +/// +/// # Input +/// ## xs +/// First $n$-bit number +/// ## ys +/// Second $n$-bit number +/// ## result +/// Will be flipped if $xs > ys$ +operation CompareGTSI(xs : Qubit[], ys : Qubit[], result : Qubit) : Unit is Adj + Ctl { + use tmp = Qubit(); + within { + CNOT(Tail(xs), tmp); + CNOT(Tail(ys), tmp); + } apply { + X(tmp); + Controlled ApplyIfGreaterLE([tmp], (X, xs, ys, result)); + X(tmp); + CCNOT(tmp, Tail(ys), result); + } +} diff --git a/library/signed/src/Measurement.qs b/library/signed/src/Measurement.qs new file mode 100644 index 0000000000..95d0213c8b --- /dev/null +++ b/library/signed/src/Measurement.qs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import Std.Diagnostics.Fact; +import Operations.Invert2sSI; + +/// # Summary +/// Measures a signed integer of a given width. +/// If the width is 4, the qubit register will be measured as a 4-bit signed integer. +/// This means that bit n is the sign bit, and bits n-1 to 0 are the integer value. +/// If fewer than `width` qubits are provided, the remaining bits are assumed to be 0. +/// For example, if qubit register `[q1, q2, q3]` is passed in, but the width is 5, +/// the integer value will be measured as `[0, 0, q1, q2, q3]`, with the first 0 being +/// the sign bit (positive). This is in contrast to the standard library `MeasureInteger`, +/// which always measures unsigned integers up to and including 63 qubits in width. +/// If the length of the qubit register passed in is greater than the width, this operation +/// will throw an error. +/// +/// # Input +/// ## target +/// A qubit register representing the signed integer to be measured. +/// +/// ## width +/// The width of the signed integer to be measured. +operation MeasureSignedInteger(target : Qubit[], width : Int) : Int { + let nBits = Length(target); + Fact(nBits <= 64, $"`Length(target)` must be less than or equal to 64, but was {nBits}."); + Fact(nBits <= width, $"`Length(target)` must be less than or equal to `width`, but was {nBits}."); + Fact(nBits > 0, $"`width` must be greater than 0, but was {width}."); + + mutable coefficient = 1; + let signBit = MResetZ(target[nBits - 1]); + if (signBit == One) { + Operations.Invert2sSI(target); + set coefficient = -1; + } + + mutable number = 0; + for i in 0..nBits - 2 { + if (MResetZ(target[i]) == One) { + set number |||= 1 <<< i; + } + } + + ResetAll(target); + + number * coefficient +} + +export MeasureSignedInteger; \ No newline at end of file diff --git a/library/signed/src/Operations.qs b/library/signed/src/Operations.qs new file mode 100644 index 0000000000..15099324d9 --- /dev/null +++ b/library/signed/src/Operations.qs @@ -0,0 +1,332 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import Std.Arrays.Tail, Std.Arrays.Most, Std.Arrays.Enumerated; +import Utils.AndLadder; +import Unstable.Arithmetic.RippleCarryTTKIncByLE; +import Std.Diagnostics.Fact; +import Unstable.Arithmetic.ApplyIfGreaterLE; + +/// # Summary +/// Square signed integer `xs` and store +/// the result in `result`, which must be zero initially. +/// +/// # Input +/// ## xs +/// ๐‘›-bit integer to square +/// ## result +/// 2๐‘›-bit result, must be in state |0โŸฉ +/// initially. +/// +/// # Remarks +/// The implementation relies on `SquareI`. +operation SquareSI(xs : Qubit[], result : Qubit[]) : Unit is Adj + Ctl { + body (...) { + Controlled SquareSI([], (xs, result)); + } + controlled (controls, ...) { + let n = Length(xs); + use signx = Qubit(); + use signy = Qubit(); + + within { + CNOT(Tail(xs), signx); + Controlled Invert2sSI([signx], xs); + } apply { + Controlled SquareI(controls, (xs, result)); + } + } +} + +/// # Summary +/// Computes the square of the integer `xs` into `result`, +/// which must be zero initially. +/// +/// # Input +/// ## xs +/// ๐‘›-bit number to square +/// ## result +/// 2๐‘›-bit result, must be in state |0โŸฉ initially. +/// +/// # Remarks +/// Uses a standard shift-and-add approach to compute the square. Saves +/// ๐‘›-1 qubits compared to the straight-forward solution which first +/// copies out `xs` before applying a regular multiplier and then undoing +/// the copy operation. +operation SquareI(xs : Qubit[], result : Qubit[]) : Unit is Adj + Ctl { + body (...) { + Controlled SquareI([], (xs, result)); + } + controlled (controls, ...) { + let n = Length(xs); + + + let numControls = Length(controls); + if numControls == 0 { + use aux = Qubit(); + for (idx, ctl) in Enumerated(xs) { + within { + CNOT(ctl, aux); + } apply { + Controlled RippleCarryTTKIncByLE([aux], (xs, (result[idx..idx + n]))); + } + } + } elif numControls == 1 { + use aux = Qubit(); + for (idx, ctl) in Enumerated(xs) { + within { + AND(controls[0], ctl, aux); + } apply { + Controlled RippleCarryTTKIncByLE([aux], (xs, (result[idx..idx + n]))); + } + } + } else { + use aux = Qubit[numControls]; + within { + AndLadder(controls, Most(aux)); + } apply { + for (idx, ctl) in Enumerated(xs) { + within { + AND(Tail(Most(aux)), ctl, Tail(aux)); + } apply { + Controlled RippleCarryTTKIncByLE([Tail(aux)], (xs, (result[idx..idx + n]))); + } + } + } + } + } +} + +/// # Summary +/// Multiply integer `xs` by integer `ys` and store the result in `result`, +/// which must be zero initially. +/// +/// # Input +/// ## xs +/// ๐‘›โ‚-bit multiplicand +/// ## ys +/// ๐‘›โ‚‚-bit multiplier +/// ## result +/// (๐‘›โ‚+๐‘›โ‚‚)-bit result, must be in state |0โŸฉ initially. +/// +/// # Remarks +/// Uses a standard shift-and-add approach to implement the multiplication. +/// The controlled version was improved by copying out ๐‘ฅแตข to an ancilla +/// qubit conditioned on the control qubits, and then controlling the +/// addition on the ancilla qubit. +operation MultiplyI(xs : Qubit[], ys : Qubit[], result : Qubit[]) : Unit is Adj + Ctl { + body (...) { + let na = Length(xs); + let nb = Length(ys); + + for (idx, actl) in Enumerated(xs) { + Controlled RippleCarryTTKIncByLE([actl], (ys, (result[idx..idx + nb]))); + } + } + controlled (controls, ...) { + let na = Length(xs); + let nb = Length(ys); + + // Perform various optimizations based on number of controls + let numControls = Length(controls); + if numControls == 0 { + MultiplyI(xs, ys, result); + } elif numControls == 1 { + use aux = Qubit(); + for (idx, actl) in Enumerated(xs) { + within { + AND(controls[0], actl, aux); + } apply { + Controlled RippleCarryTTKIncByLE([aux], (ys, (result[idx..idx + nb]))); + } + } + } else { + use helper = Qubit[numControls]; + within { + AndLadder(controls, Most(helper)); + } apply { + for (idx, actl) in Enumerated(xs) { + within { + AND(Tail(Most(helper)), actl, Tail(helper)); + } apply { + Controlled RippleCarryTTKIncByLE([Tail(helper)], (ys, (result[idx..idx + nb]))); + } + } + } + } + } +} + +/// # Summary +/// Multiply signed integer `xs` by signed integer `ys` and store +/// the result in `result`, which must be zero initially. +/// +/// # Input +/// ## xs +/// ๐‘›โ‚-bit multiplicand +/// ## ys +/// ๐‘›โ‚‚-bit multiplier +/// ## result +/// (๐‘›โ‚+๐‘›โ‚‚)-bit result, must be in state |0โŸฉ +/// initially. +operation MultiplySI(xs : Qubit[], ys : Qubit[], result : Qubit[]) : Unit is Adj + Ctl { + body (...) { + Controlled MultiplySI([], (xs, ys, result)); + } + controlled (controls, ...) { + use signx = Qubit(); + use signy = Qubit(); + + within { + CNOT(Tail(xs), signx); + CNOT(Tail(ys), signy); + Controlled Invert2sSI([signx], xs); + Controlled Invert2sSI([signy], ys); + } apply { + Controlled MultiplyI(controls, (xs, ys, result)); + within { + CNOT(signx, signy); + } apply { + // No controls required since `result` will still be zero + // if we did not perform the multiplication above. + Controlled Invert2sSI([signy], result); + } + } + } +} + + + +/// # Summary +/// Implements a reversible sum gate. Given a carry-in bit encoded in +/// qubit `carryIn` and two summand bits encoded in `summand1` and `summand2`, +/// computes the bitwise xor of `carryIn`, `summand1` and `summand2` in the qubit +/// `summand2`. +/// +/// # Input +/// ## carryIn +/// Carry-in qubit. +/// ## summand1 +/// First summand qubit. +/// ## summand2 +/// Second summand qubit, is replaced with the lower bit of the sum of +/// `summand1` and `summand2`. +/// +/// # Remarks +/// In contrast to the `Carry` operation, this does not compute the carry-out bit. +operation Sum(carryIn : Qubit, summand1 : Qubit, summand2 : Qubit) : Unit is Adj + Ctl { + CNOT(summand1, summand2); + CNOT(carryIn, summand2); +} + +/// # Summary +/// Inverts a given integer modulo 2's complement. +/// +/// # Input +/// ## xs +/// n-bit signed integer (SignedLittleEndian), will be inverted modulo +/// 2's complement. +operation Invert2sSI(xs : Qubit[]) : Unit is Adj + Ctl { + body (...) { + Controlled Invert2sSI([], xs); + } + controlled (controls, ...) { + ApplyToEachCA((Controlled X)(controls, _), xs); + + use aux = Qubit[Length(xs)]; + within { + Controlled X(controls, aux[0]); + } apply { + RippleCarryTTKIncByLE(aux, xs); + } + } +} + +/// # Summary +/// Divides two quantum integers. +/// +/// # Description +/// `xs` will hold the +/// remainder `xs - floor(xs/ys) * ys` and `result` will hold +/// `floor(xs/ys)`. +/// +/// # Input +/// ## xs +/// $n$-bit dividend, will be replaced by the remainder. +/// ## ys +/// $n$-bit divisor +/// ## result +/// $n$-bit result, must be in state $\ket{0}$ initially +/// and will be replaced by the result of the integer division. +/// +/// # Remarks +/// Uses a standard shift-and-subtract approach to implement the division. +/// The controlled version is specialized such the subtraction does not +/// require additional controls. +operation DivideI(xs : Qubit[], ys : Qubit[], result : Qubit[]) : Unit is Adj + Ctl { + body (...) { + Controlled DivideI([], (xs, ys, result)); + } + controlled (controls, ...) { + let n = Length(result); + + Fact(n == Length(ys), "Integer division requires + equally-sized registers ys and result."); + Fact(n == Length(xs), "Integer division + requires an n-bit dividend registers."); + + let xpadded = xs + result; + + for i in (n - 1)..(-1)..0 { + let xtrunc = xpadded[i..i + n-1]; + Controlled ApplyIfGreaterLE(controls, (X, ys, xtrunc, result[i])); + // if ys > xtrunc, we don't subtract: + (Controlled X)(controls, result[i]); + (Controlled Adjoint RippleCarryTTKIncByLE)([result[i]], (ys, xtrunc)); + } + } +} + +/// # Summary +/// Computes the reciprocal 1/x for an unsigned integer x +/// using integer division. The result, interpreted as an integer, +/// will be `floor(2^(2*n-1) / x)`. +/// +/// # Input +/// ## xs +/// n-bit unsigned integer +/// ## result +/// 2n-bit output, must be in $\ket{0}$ initially. +/// +/// # Remarks +/// For the input x=0, the output will be all-ones. +operation ComputeReciprocalI(xs : Qubit[], result : Qubit[]) : Unit is Adj + Ctl { + body (...) { + Controlled ComputeReciprocalI([], (xs, result)); + } + controlled (controls, ...) { + let n = Length(xs); + Fact(Length(result) == 2 * n, "Result register must contain 2n qubits."); + use lhs = Qubit[2 * n]; + use padding = Qubit[n]; + let paddedxs = xs + padding; + X(Tail(lhs)); // initialize left-hand side to 2^{2n-1} + // ... and divide: + (Controlled DivideI)(controls, (lhs, paddedxs, result)); + // uncompute lhs + for i in 0..2 * n - 1 { + (Controlled RippleCarryTTKIncByLE)([result[i]], (paddedxs[0..2 * n-1-i], lhs[i..2 * n-1])); + } + X(Tail(lhs)); + } +} + +export + Sum, + MultiplyI, + MultiplySI, + SquareSI, + SquareI, + Invert2sSI, + DivideI, + ComputeReciprocalI; \ No newline at end of file diff --git a/library/signed/src/Tests.qs b/library/signed/src/Tests.qs new file mode 100644 index 0000000000..3f8b166a61 --- /dev/null +++ b/library/signed/src/Tests.qs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import Std.Diagnostics.Fact; +import Operations.Invert2sSI; +import Measurement.MeasureSignedInteger; + +/// This entrypoint runs tests for the signed integer library. +operation Main() : Unit { + UnsignedOpTests(); + MeasureSignedIntTests(); + SignedOpTests(); + +} + +operation MeasureSignedIntTests() : Unit { + use a = Qubit[4]; + + // 0b0001 == 1 + X(a[0]); + let res = MeasureSignedInteger(a, 4); + Fact(res == 1, $"Expected 1, received {res}"); + + // 0b1111 == -1 + X(a[0]); + X(a[1]); + X(a[2]); + X(a[3]); + let res = MeasureSignedInteger(a, 4); + Fact(res == -1, $"Expected -1, received {res}"); + + // 0b01000 == 8 + use a = Qubit[5]; + X(a[3]); + let res = MeasureSignedInteger(a, 5); + Fact(res == 8, $"Expected 8, received {res}"); + + // 0b11110 == -2 + X(a[1]); + X(a[2]); + X(a[3]); + X(a[4]); + let res = MeasureSignedInteger(a, 5); + Fact(res == -2, $"Expected -2, received {res}"); + + // 0b11000 == -8 + X(a[3]); + X(a[4]); + let res = MeasureSignedInteger(a, 5); + Fact(res == -8, $"Expected -8, received {res}"); + +} + +operation SignedOpTests() : Unit { + use a = Qubit[32]; + use b = Qubit[32]; + use c = Qubit[64]; + + // 0b11111110 (-2 in twos complement) * 0b00000001 == 0b11111110 (-2) + X(a[1]); + Operations.Invert2sSI(a); + X(b[0]); + TestSignedIntOp(Operations.MultiplySI, a, b, c, -2); + + // 0b11111110 (-2 in twos complement) * 0b11111111 (-1 in twos complement) == 0b00000010 (2) + X(a[1]); + Operations.Invert2sSI(a); + X(b[0]); + Operations.Invert2sSI(b); + TestSignedIntOp(Operations.MultiplySI, a, b, c, 2); + + + // 0b11111110 (-2 in twos complement) squared is 0b00000100 (4) + X(a[1]); + Operations.Invert2sSI(a); + TestSignedIntOp((a, b, _) => Operations.SquareSI(a, c), a, b, c, 4); + +} + +operation UnsignedOpTests() : Unit { + use a = Qubit[2]; + use b = Qubit[2]; + use c = Qubit[4]; + + // 0b10 * 0b01 == 0b10 (1 * 2 = 2) + X(a[0]); + X(b[1]); + TestIntOp(Operations.MultiplyI, a, b, c, 2); + + // 0b01 * 0b10 == 0b10 (1 * 2 = 2) + X(a[1]); + X(b[0]); + TestIntOp(Operations.MultiplyI, a, b, c, 2); + + // 0b11 * 0b11 == 0b1001 (3 * 3 = 9) + X(a[0]); + X(b[0]); + X(a[1]); + X(b[1]); + TestIntOp(Operations.MultiplyI, a, b, c, 9); + + + use a = Qubit[8]; + use b = Qubit[8]; + use c = Qubit[16]; + + // 0b00001010 * 0b00001011 == 0b01100100 (10 * 11 = 110) + X(a[1]); + X(a[3]); + X(b[1]); + X(b[3]); + X(b[0]); + TestIntOp(Operations.MultiplyI, a, b, c, 110); + + // 0b00001010 ^ 2 = 0b01100100 (10 ^ 2 = 100) + X(a[1]); + X(a[3]); + TestIntOp((a, b, _) => Operations.SquareI(a, c), a, b, c, 100); + + // 0b00001010 / 0b00000010 == 0b00000101 (10 / 2 = 5) + X(a[1]); + X(a[3]); + X(b[1]); + // need smaller result register for div, mod, etc + use d = Qubit[8]; + TestIntOp(Operations.DivideI, a, b, d, 5); +} + +operation TestIntOp(op : (Qubit[], Qubit[], Qubit[]) => Unit, a : Qubit[], b : Qubit[], c : Qubit[], expect : Int) : Unit { + op(a, b, c); + let res = MeasureInteger(c); + Fact(res == expect, $"Expected {expect}, got {res}"); + ResetAll(a + b + c); +} + +operation TestSignedIntOp(op : (Qubit[], Qubit[], Qubit[]) => Unit, a : Qubit[], b : Qubit[], c : Qubit[], expect : Int) : Unit { + op(a, b, c); + let res = MeasureSignedInteger(c, 64); + Fact(res == expect, $"Expected {expect}, got {res}"); + ResetAll(a + b + c); +} diff --git a/library/signed/src/Utils.qs b/library/signed/src/Utils.qs new file mode 100644 index 0000000000..7c41e14b64 --- /dev/null +++ b/library/signed/src/Utils.qs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Util functions for internal use by the signed integer library. + +import Std.Math.Min; +import Std.Diagnostics.Fact; +import Std.Arrays.Head, Std.Arrays.Tail, Std.Arrays.Most, Std.Arrays.Rest; + +operation AndLadder(controls : Qubit[], targets : Qubit[]) : Unit is Adj { + Fact(Length(controls) == Length(targets), "The number of control qubits must match the number of target qubits."); + let controls1 = [Head(controls)] + Most(targets); + let controls2 = Rest(controls); + for i in 0..Length(controls1) - 1 { + AND(controls1[i], controls2[i], targets[i]); + } +} \ No newline at end of file