From 353bd64d722f77819e99c5361cac61cf5662599d Mon Sep 17 00:00:00 2001 From: DmitryVasilevsky <60718360+DmitryVasilevsky@users.noreply.github.com> Date: Fri, 8 Dec 2023 00:28:32 -0800 Subject: [PATCH] Added comparisons to Arithmetic namespace (#893) Added comparisons and tests. --------- Co-authored-by: Dmitry Vasilevsky --- library/std/convert.qs | 34 ++- library/std/unstable_arithmetic.qs | 223 +++++++++++++++++++- library/std/unstable_arithmetic_internal.qs | 131 ++++++++++++ library/tests/src/resources/compare.qs | 59 ++++++ library/tests/src/test_arithmetic.rs | 167 +++++++++++++++ 5 files changed, 611 insertions(+), 3 deletions(-) create mode 100644 library/tests/src/resources/compare.qs diff --git a/library/std/convert.qs b/library/std/convert.qs index 42c3ff3330..048d9ef325 100644 --- a/library/std/convert.qs +++ b/library/std/convert.qs @@ -73,12 +73,12 @@ namespace Microsoft.Quantum.Convert { /// /// # Input /// ## number - /// A non-negative integer to be converted to an array of boolean values. + /// A non-negative integer to be converted to an array of Boolean values. /// ## bits /// The number of bits in the binary representation of `number`. /// /// # Output - /// An array of boolean values representing `number`. + /// An array of Boolean values representing `number`. /// /// # Remarks /// The input `bits` must be non-negative. @@ -97,6 +97,36 @@ namespace Microsoft.Quantum.Convert { result } + /// # Summary + /// Produces a binary representation of a non-negative BigInt, using the + /// little-endian representation for the returned array. + /// + /// # Input + /// ## number + /// A non-negative BigInt to be converted to an array of Boolean values. + /// ## bits + /// The number of bits in the binary representation of `number`. + /// + /// # Output + /// An array of Boolean values representing `number`. + /// + /// # Remarks + /// The input `bits` must be non-negative. + /// The input `number` must be between 0 and 2^bits - 1. + function BigIntAsBoolArray(number : BigInt, bits : Int) : Bool[] { + Fact(bits >= 0, "Requested number of bits must be non-negative."); + Fact(number >= 0L, "Number must be non-negative."); + mutable runningValue = number; + mutable result = []; + for _ in 1..bits { + set result += [ (runningValue &&& 1L) != 0L ]; + set runningValue >>>= 1; + } + Fact(runningValue == 0L, $"`number`={number} is too large to fit into {bits} bits."); + + result + } + /// # Summary /// Produces a non-negative integer from a string of Results in little-endian format. /// diff --git a/library/std/unstable_arithmetic.qs b/library/std/unstable_arithmetic.qs index b74f78fc2b..cde9477548 100644 --- a/library/std/unstable_arithmetic.qs +++ b/library/std/unstable_arithmetic.qs @@ -6,6 +6,7 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { open Microsoft.Quantum.Arrays; open Microsoft.Quantum.Diagnostics; open Microsoft.Quantum.Math; + open Microsoft.Quantum.Convert; /// # Summary /// This applies the in-place majority operation to 3 qubits. @@ -511,4 +512,224 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { } } -} \ No newline at end of file + // + // Compare BigInt and qubit register in a little-endian format + // + // target ^= c < x | InvertIfLessL + // target ^= c <= x | InvertIfLessOrEqualL + // target ^= c == x | InvertIfEqualL + // target ^= c >= x | InvertIfGreaterOrEqualL + // target ^= c > x | InvertIfGreaterL + // + + /// # Summary + /// Computes `target ^= (c < x)`, that is, inverts `target` + /// if a BigInt value `c` is less than the little-endian qubit register `x` + operation InvertIfLessL (c : BigInt, x : Qubit[], target : Qubit) : Unit is Adj + Ctl { + ApplyActionIfGreaterThanOrEqualConstant(false, X, c + 1L, x, target); + } + + /// # Summary + /// Computes `target ^= (c <= x)`, that is, inverts `target` + /// if a BigInt value `c` is less or equal to the little-endian qubit register `x` + operation InvertIfLessOrEqualL (c : BigInt, x : Qubit[], target : Qubit) : Unit is Adj + Ctl { + ApplyActionIfGreaterThanOrEqualConstant(false, X, c, x, target); + } + + /// # Summary + /// Computes `target ^= (c == x)`, that is, inverts `target` + /// if a BigInt value `c` is equal to the little-endian qubit register `x` + operation InvertIfEqualL (c : BigInt, xs : Qubit[], target : Qubit) : Unit is Adj + Ctl { + let cBitSize = BitSizeL(c); + let xLen = Length(xs); + if (cBitSize <= xLen) { + let bits = BigIntAsBoolArray(c, Length(xs)); + within { + ApplyPauliFromBitString(PauliX, false, bits, xs); + } apply { + Controlled X(xs, target); + } + } + } + + /// # Summary + /// Computes `target ^= (c >= x)`, that is, inverts `target` + /// if a BigInt value `c` is greater or equal to the little-endian qubit register `x` + operation InvertIfGreaterOrEqualL (c : BigInt, x : Qubit[], target : Qubit) : Unit is Adj + Ctl { + ApplyActionIfGreaterThanOrEqualConstant(true, X, c + 1L, x, target); + } + + /// # Summary + /// Computes `target ^= (c > x)`, that is, inverts `target` + /// if a BigInt value `c` is greater than the little-endian qubit register `x` + operation InvertIfGreaterL (c : BigInt, x : Qubit[], target : Qubit) : Unit is Adj + Ctl { + ApplyActionIfGreaterThanOrEqualConstant(true, X, c, x, target); + } + + // + // Compare two qubit registers in a little-endian format + // + // target ^= x < y | InvertIfLessLE + // target ^= x <= y | InvertIfLessOrEqualLE + // target ^= x == y | InvertIfEqualLE + // target ^= x >= y | InvertIfGreaterOrEqualLE + // target ^= x > y | InvertIfGreaterLE + // + + /// # Summary + /// Computes `target ^= (x < y)`, that is, inverts `target` + /// if register `x` is less than the register `y`. + /// Both qubit registers should be in a little-endian format. + operation InvertIfLessLE (x : Qubit[], y : Qubit[], target : Qubit) : Unit is Adj + Ctl { + InvertIfGreaterLE(y, x, target); + } + + /// # Summary + /// Computes `target ^= (x <= y)`, that is, inverts `target` + /// if register `x` is less or equal to the register `y`. + /// Both qubit registers should be in a little-endian format. + operation InvertIfLessOrEqualLE (x : Qubit[], y : Qubit[], target : Qubit) : Unit is Adj + Ctl { + Fact(Length(x) > 0, "Bitwidth must be at least 1"); + + within { + ApplyToEachA(X, x); + } apply { + ApplyActionIfSumOverflows(X, x, y, false, target); + } + } + + /// # Summary + /// Computes `target ^= (x == y)`, that is, inverts `target` + /// if register `x` is equal to the register `y`. + /// Both qubit registers should be in a little-endian format. + operation InvertIfEqualLE (x : Qubit[], y : Qubit[], target : Qubit) : Unit is Adj + Ctl { + Fact(Length(x) == Length(y), "x and y must be of same length"); + + within { + for i in IndexRange(x) { + CNOT(x[i], y[i]); + X(y[i]); + } + } apply { + Controlled X(y, target); + } + } + + /// # Summary + /// Computes `target ^= (x >= y)`, that is, inverts `target` + /// if register `x` is greater or equal to the register `y`. + /// Both qubit registers should be in a little-endian format. + operation InvertIfGreaterOrEqualLE (x : Qubit[], y : Qubit[], target : Qubit) : Unit is Adj + Ctl { + InvertIfLessOrEqualLE(y, x, target); + } + + /// # Summary + /// Computes `target ^= (x > y)`, that is, inverts `target` + /// if register `x` is greater than the register `y`. + /// Both qubit registers should be in a little-endian format. + operation InvertIfGreaterLE (x : Qubit[], y : Qubit[], target : Qubit) : Unit is Adj + Ctl { + Fact(Length(x) > 0, "Bitwidth must be at least 1"); + + within { + ApplyToEachA(X, x); + } apply { + ApplyActionIfSumOverflows(X, x, y, true, target); + } + } + + // + // Compare two qubit registers in a little-endian format and apply action + // + // if x < y { action(target) } | ApplyIfLessLE + // if x <= y { action(target) } | ApplyIfLessOrEqualLE + // if x == y { action(target) } | ApplyIfEqualLE + // if x >= y { action(target) } | ApplyIfGreaterOrEqualLE + // if x > y { action(target) } | ApplyIfGreaterLE + // + + /// # Summary + /// Computes `if x < y { action(target) }`, that is, applies `action` to `target` + /// if register `x` is less than the register `y`. + /// Both qubit registers should be in a little-endian format. + operation ApplyIfLessLE<'T> ( + action : 'T => Unit is Adj + Ctl, + x : Qubit[], + y : Qubit[], + target : 'T) : Unit is Adj + Ctl { + + ApplyIfGreaterLE(action, y, x, target); + } + + /// # Summary + /// Computes `if x <= y { action(target) }`, that is, applies `action` to `target` + /// if register `x` is less or equal to the register `y`. + /// Both qubit registers should be in a little-endian format. + operation ApplyIfLessOrEqualLE<'T> ( + action : 'T => Unit is Adj + Ctl, + x : Qubit[], + y : Qubit[], + target : 'T) : Unit is Adj + Ctl { + + Fact(Length(x) > 0, "Bitwidth must be at least 1"); + within { + ApplyToEachA(X, x); + } apply { + // control is not inverted + ApplyActionIfSumOverflows(action, x, y, false, target); + } + } + + /// # Summary + /// Computes `x == y { action(target) }`, that is, applies `action` to `target` + /// if register `x` is equal to the register `y`. + /// Both qubit registers should be in a little-endian format. + operation ApplyIfEqualLE<'T> ( + action : 'T => Unit is Adj + Ctl, + x : Qubit[], + y : Qubit[], + target : 'T) : Unit is Adj + Ctl { + + Fact(Length(x) == Length(y), "x and y must be of same length"); + within { + for i in IndexRange(x) { + CNOT(x[i], y[i]); + X(y[i]); + } + } apply { + Controlled action(y, target); + } + } + + /// # Summary + /// Computes `if x >= y { action(target) }`, that is, applies `action` to `target` + /// if register `x` is greater or equal to the register `y`. + /// Both qubit registers should be in a little-endian format. + operation ApplyIfGreaterOrEqualLE<'T> ( + action : 'T => Unit is Adj + Ctl, + x : Qubit[], + y : Qubit[], + target : 'T) : Unit is Adj + Ctl { + + ApplyIfLessOrEqualLE(action, y, x, target); + } + + /// # Summary + /// Computes `if x > y { action(target) }`, that is, applies `action` to `target` + /// if register `x` is greater than the register `y`. + /// Both qubit registers should be in a little-endian format. + operation ApplyIfGreaterLE<'T> ( + action : 'T => Unit is Adj + Ctl, + x : Qubit[], + y : Qubit[], + target : 'T) : Unit is Adj + Ctl { + + Fact(Length(x) > 0, "Bitwidth must be at least 1"); + within { + ApplyToEachA(X, x); + } apply { + // control is inverted + ApplyActionIfSumOverflows(action, x, y, true, target); + } + } + +} diff --git a/library/std/unstable_arithmetic_internal.qs b/library/std/unstable_arithmetic_internal.qs index a84f0aa4e7..3dfc33bab6 100644 --- a/library/std/unstable_arithmetic_internal.qs +++ b/library/std/unstable_arithmetic_internal.qs @@ -272,6 +272,16 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { } } + internal operation ApplyOrAssuming0Target(control1 : Qubit, control2 : Qubit, target : Qubit) : Unit is Adj { + within { + X(control1); + X(control2); + } apply { + ApplyAndAssuming0Target(control1, control2, target); + X(target); + } + } + /// # Summary /// Applies AND gate between `control1` and `control2` and stores the result /// in `target` assuming `target` is in |0> state. @@ -404,4 +414,125 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { } } + // + // Internal operations for comparisons + // + + /// # Summary + /// Applies `action` to `target` if register `x` is greater or equal to BigInt `c` + /// (if `invertControl` is false). If `invertControl` is true, the `action` + /// is applied in the opposite situation. + internal operation ApplyActionIfGreaterThanOrEqualConstant<'T>( + invertControl: Bool, + action: 'T => Unit is Adj + Ctl, + c: BigInt, + x: Qubit[], + target: 'T) : Unit is Adj + Ctl { + + let bitWidth = Length(x); + if c == 0L { + if not invertControl { + action(target); + } + } elif c >= (2L^bitWidth) { + if invertControl { + action(target); + } + } else { + // normalize constant + let l = TrailingZeroCountL(c); + + let cNormalized = c >>> l; + let xNormalized = x[l...]; + let bitWidthNormalized = Length(xNormalized); + + // If c == 2L^(bitwidth - 1), then bitWidthNormalized will be 1, + // and qs will be empty. In that case, we do not need to compute + // any temporary values, and some optimizations are apply, which + // are considered in the remainder. + use qs = Qubit[bitWidthNormalized - 1]; + let cs1 = IsEmpty(qs) ? [] | [Head(xNormalized)] + Most(qs); + + Fact(Length(cs1) == Length(qs), + "Arrays should be of the same length."); + + within { + for i in 0..Length(cs1)-1 { + let op = + cNormalized &&& (1L <<< (i+1)) != 0L ? + ApplyAndAssuming0Target | ApplyOrAssuming0Target; + op(cs1[i], xNormalized[i+1], qs[i]); + } + } apply { + let control = IsEmpty(qs) ? Tail(x) | Tail(qs); + within { + if invertControl { + X(control); + } + } apply { + Controlled action([control], target); + } + } + } + } + + /// # Summary + /// Applies `action` to `target` if the sum of `x` and `y` registers + /// overflows, i.e. there's a carry out (if `invertControl` is false). + /// If `invertControl` is true, the `action` is applied when there's no carry out. + internal operation ApplyActionIfSumOverflows<'T> ( + action : 'T => Unit is Adj + Ctl, + x : Qubit[], + y : Qubit[], + invertControl : Bool, + target : 'T) : Unit is Adj + Ctl { + + let n = Length(x); + Fact(n >= 1, "Registers must contain at least one qubit."); + Fact(Length(y) == n, "Registers must be of the same length."); + + use carries = Qubit[n]; + + within { + CarryWith1CarryIn(x[0], y[0], carries[0]); + for i in 1..n-1 { + CarryForInc(carries[i-1], x[i], y[i], carries[i]); + } + } apply { + within { + if invertControl { + X(carries[n-1]); + } + } apply { + Controlled action([carries[n-1]], target); + } + } + } + + /// # Summary + /// Computes carry out assuming carry in is 1. + /// Simplified version that is only applicable for scenarios + /// where controlled version is the same as non-controlled. + internal operation CarryWith1CarryIn( + x : Qubit, + y : Qubit, + carryOut : Qubit) : Unit is Adj + Ctl { + + body (...) { + X(x); + X(y); + ApplyAndAssuming0Target(x, y, carryOut); + X(carryOut); + } + + adjoint auto; + + controlled (ctls, ...) { + Fact(Length(ctls) <= 1, "Number of control lines must be at most 1"); + CarryWith1CarryIn(x, y, carryOut); + } + + controlled adjoint auto; + } + } diff --git a/library/tests/src/resources/compare.qs b/library/tests/src/resources/compare.qs new file mode 100644 index 0000000000..6a0cec9030 --- /dev/null +++ b/library/tests/src/resources/compare.qs @@ -0,0 +1,59 @@ +namespace Test { + open Microsoft.Quantum.Measurement; + open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Unstable.Arithmetic; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Diagnostics; + + internal operation CompareWithBigInt( + name: String, + bitwidth : Int, + quantumComparator : (BigInt, Qubit[], Qubit) => Unit, + classicalComparator : (Int, Int) -> Bool) : Unit { + + for n in 1..bitwidth { + use qs = Qubit[n]; + use t = Qubit(); + + for a in 0 .. 2^n+1 { // We want b to have more bits sometimes... + for b in 0 .. 2^n-1 { + ApplyXorInPlace(b, qs); + quantumComparator(IntAsBigInt(a), qs, t); + let actual = MResetZ(t) == One; + let expected = classicalComparator(a, b); + Fact(actual == expected, + $"{name}: Wrong result {actual}, expected {expected}. bitwidth={n}, a={a}, b={b}."); + ResetAll(qs); + } + } + } + } + + internal operation CompareWithLE( + name: String, + bitwidth : Int, + quantumComparator : (Qubit[], Qubit[], Qubit) => Unit, + classicalComparator : (Int, Int) -> Bool) : Unit { + + for n in 1..bitwidth { + use x = Qubit[n]; + use y = Qubit[n]; + use t = Qubit(); + + for a in 0 .. 2^n-1 { + for b in 0 .. 2^n-1 { + ApplyXorInPlace(a, x); + ApplyXorInPlace(b, y); + quantumComparator(x, y, t); + let actual = MResetZ(t) == One; + let expected = classicalComparator(a, b); + Fact(actual == expected, + $"{name}: Wrong result {actual}, expected {expected}. bitwidth={n}, a={a}, b={b}."); + ResetAll(x); + ResetAll(y); + } + } + } + } + +} diff --git a/library/tests/src/test_arithmetic.rs b/library/tests/src/test_arithmetic.rs index 62749ddf63..09e7e28ca2 100644 --- a/library/tests/src/test_arithmetic.rs +++ b/library/tests/src/test_arithmetic.rs @@ -902,3 +902,170 @@ fn check_lookahead_dkrs_add_le_general() { &Value::Int(939 + 578), ); } + +const COMPARE_TEST_LIB: &str = include_str!("resources/compare.qs"); + +#[test] +fn check_invert_if_less_l_exhaustive() { + test_expression_with_lib( + "Test.CompareWithBigInt(\"Check InvertIfLessL\", 3, + Microsoft.Quantum.Unstable.Arithmetic.InvertIfLessL, + (a, b) -> {a < b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_invert_if_less_or_equal_l_exhaustive() { + test_expression_with_lib( + "Test.CompareWithBigInt(\"Check InvertIfLessOrEqualL\", 3, + Microsoft.Quantum.Unstable.Arithmetic.InvertIfLessOrEqualL, + (a, b) -> {a <= b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_invert_if_equal_l_exhaustive() { + test_expression_with_lib( + "Test.CompareWithBigInt(\"Check InvertIfEqualL\", 3, + Microsoft.Quantum.Unstable.Arithmetic.InvertIfEqualL, + (a, b) -> {a == b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_invert_if_greater_or_equal_l_exhaustive() { + test_expression_with_lib( + "Test.CompareWithBigInt(\"Check InvertIfGreaterOrEqualL\", 3, + Microsoft.Quantum.Unstable.Arithmetic.InvertIfGreaterOrEqualL, + (a, b) -> {a >= b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_invert_if_greater_l_exhaustive() { + test_expression_with_lib( + "Test.CompareWithBigInt(\"Check InvertIfGreaterL\", 3, + Microsoft.Quantum.Unstable.Arithmetic.InvertIfGreaterL, + (a, b) -> {a > b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_invert_if_less_le_exhaustive() { + test_expression_with_lib( + "Test.CompareWithLE(\"Check InvertIfLessLE\", 3, + Microsoft.Quantum.Unstable.Arithmetic.InvertIfLessLE, + (a, b) -> {a < b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_invert_if_less_or_equal_le_exhaustive() { + test_expression_with_lib( + "Test.CompareWithLE(\"Check InvertIfLessOrEqualLE\", 3, + Microsoft.Quantum.Unstable.Arithmetic.InvertIfLessOrEqualLE, + (a, b) -> {a <= b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_invert_if_equal_le_exhaustive() { + test_expression_with_lib( + "Test.CompareWithLE(\"Check InvertIfEqualLE\", 3, + Microsoft.Quantum.Unstable.Arithmetic.InvertIfEqualLE, + (a, b) -> {a == b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_invert_if_greater_or_equal_le_exhaustive() { + test_expression_with_lib( + "Test.CompareWithLE(\"Check InvertIfGreaterOrEqualLE\", 3, + Microsoft.Quantum.Unstable.Arithmetic.InvertIfGreaterOrEqualLE, + (a, b) -> {a >= b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_invert_if_greater_le_exhaustive() { + test_expression_with_lib( + "Test.CompareWithLE(\"Check InvertIfGreaterLE\", 3, + Microsoft.Quantum.Unstable.Arithmetic.InvertIfGreaterLE, + (a, b) -> {a > b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_apply_if_less_le_exhaustive() { + test_expression_with_lib( + "Test.CompareWithLE(\"Check ApplyIfLessLE\", 3, + Microsoft.Quantum.Unstable.Arithmetic.ApplyIfLessLE(X,_,_,_), + (a, b) -> {a < b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_apply_if_less_or_equal_le_exhaustive() { + test_expression_with_lib( + "Test.CompareWithLE(\"Check ApplyIfLessOrEqualLE\", 3, + Microsoft.Quantum.Unstable.Arithmetic.ApplyIfLessOrEqualLE(X,_,_,_), + (a, b) -> {a <= b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_apply_if_equal_le_exhaustive() { + test_expression_with_lib( + "Test.CompareWithLE(\"Check ApplyIfEqualLE\", 3, + Microsoft.Quantum.Unstable.Arithmetic.ApplyIfEqualLE(X,_,_,_), + (a, b) -> {a == b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_apply_if_greater_or_equal_le_exhaustive() { + test_expression_with_lib( + "Test.CompareWithLE(\"Check ApplyIfGreaterOrEqualLE\", 3, + Microsoft.Quantum.Unstable.Arithmetic.ApplyIfGreaterOrEqualLE(X,_,_,_), + (a, b) -> {a >= b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +} + +#[test] +fn check_apply_if_greater_le_exhaustive() { + test_expression_with_lib( + "Test.CompareWithLE(\"Check ApplyIfGreaterLE\", 3, + Microsoft.Quantum.Unstable.Arithmetic.ApplyIfGreaterLE(X,_,_,_), + (a, b) -> {a > b} )", + COMPARE_TEST_LIB, + &Value::Tuple(vec![].into()), + ); +}