From 768dfb69733c57cad38517a62aa3e217432e6fc6 Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Sun, 17 Apr 2022 11:04:53 +0200 Subject: [PATCH 1/4] Implements #465. --- Numerics/src/Integer/Multiplication.qs | 83 +++++++++++++------ Numerics/tests/IntegerHighLevelTests.qs | 106 +++++++++++++----------- 2 files changed, 116 insertions(+), 73 deletions(-) diff --git a/Numerics/src/Integer/Multiplication.qs b/Numerics/src/Integer/Multiplication.qs index 01383d19bfc..62ed46c333c 100644 --- a/Numerics/src/Integer/Multiplication.qs +++ b/Numerics/src/Integer/Multiplication.qs @@ -3,6 +3,7 @@ namespace Microsoft.Quantum.Arithmetic { open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Canon; open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Diagnostics; @@ -12,50 +13,85 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// $n$-bit multiplicand (LittleEndian) + /// ๐‘›โ‚-bit multiplicand (LittleEndian) /// ## ys - /// $n$-bit multiplier (LittleEndian) + /// ๐‘›โ‚‚-bit multiplier (LittleEndian) /// ## result - /// $2n$-bit result (LittleEndian), must be in state $\ket{0}$ initially. + /// (๐‘›โ‚+๐‘›โ‚‚)-bit result (LittleEndian), 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 $x_i$ to an ancilla + /// 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: LittleEndian, ys: LittleEndian, - result: LittleEndian) : Unit is Adj + Ctl { + operation MultiplyI(xs: LittleEndian, ys: LittleEndian, result: LittleEndian) : Unit is Adj + Ctl { body (...) { - let n = Length(xs!); + let na = Length(xs!); + let nb = Length(ys!); - EqualityFactI(n, Length(ys!), "Integer multiplication requires - equally-sized registers xs and ys."); - EqualityFactI(2 * n, Length(result!), "Integer multiplication - requires a 2n-bit result registers."); + EqualityFactI(na + nb, Length(result!), "Integer multiplication requires a register as long as both input registers added"); AssertAllZero(result!); - for i in 0..n-1 { - Controlled AddI([xs![i]], (ys, LittleEndian(result![i..i+n]))); + for (idx, actl) in Enumerated(xs!) { + Controlled AddI([actl], (ys, LittleEndian(result![idx..idx + nb]))); } } controlled (controls, ...) { - let n = Length(xs!); + let na = Length(xs!); + let nb = Length(ys!); - EqualityFactI(n, Length(ys!), "Integer multiplication requires - equally-sized registers xs and ys."); - EqualityFactI(2 * n, Length(result!), "Integer multiplication - requires a 2n-bit result registers."); + EqualityFactI(na + nb, Length(result!), "Integer multiplication requires a register as long as both input registers added"); AssertAllZero(result!); - use aux = Qubit(); - for i in 0..n - 1 { - (Controlled CNOT) (controls, (xs![i], aux)); - (Controlled AddI) ([aux], (ys, LittleEndian(result![i..i+n]))); - (Controlled CNOT) (controls, (xs![i], aux)); + // 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 { + ApplyAnd(controls[0], actl, aux); + } apply { + Controlled AddI([aux], (ys, LittleEndian(result![idx..idx + nb]))); + } + } + } else { + use helper = Qubit[numControls]; + within { + ApplyAndLadder(controls, Most(helper)); + } apply { + for (idx, actl) in Enumerated(xs!) { + within { + ApplyAnd(Tail(Most(helper)), actl, Tail(helper)); + } apply { + Controlled AddI([Tail(helper)], (ys, LittleEndian(result![idx..idx + nb]))); + } + } + } } } } + /// # Summary + /// Applies AND of at least 2 inputs on a target in |0โŸฉ state. + /// + /// # Inputs + /// ## controls + /// At least two control qubits + /// ## targets + /// All intermediate targets and the final AND in the last qubit of that register. + /// The size of `targets` must be one less than the size of `controls`. + internal operation ApplyAndLadder(controls : Qubit[], targets : Qubit[]) : Unit is Adj { + // TODO: This operation should be moved to M.Q.Canon in Standard after API review + EqualityFactI(Length(controls), Length(targets) + 1, "there must be one more control qubit than target qubits"); + Fact(Length(controls) >= 2, "there must be at least 2 control qubits"); + + let controls1 = [Head(controls)] + Most(targets); + let controls2 = Rest(controls); + ApplyToEachA(ApplyAnd, Zipped3(controls1, controls2, targets)); + } + /// # Summary /// Computes the square of the integer `xs` into `result`, /// which must be zero initially. @@ -113,7 +149,6 @@ namespace Microsoft.Quantum.Arithmetic { Controlled MultiplySI([], (xs, ys, result)); } controlled (controls, ...) { - let n = Length(xs!!); use signx = Qubit(); use signy = Qubit(); diff --git a/Numerics/tests/IntegerHighLevelTests.qs b/Numerics/tests/IntegerHighLevelTests.qs index fe531b4e029..5b4e505b4b5 100644 --- a/Numerics/tests/IntegerHighLevelTests.qs +++ b/Numerics/tests/IntegerHighLevelTests.qs @@ -8,7 +8,7 @@ namespace Microsoft.Quantum.Numerics.ToffoliTests { open Microsoft.Quantum.Extensions.Math; operation MultiplyIExhaustiveTest() : Unit { - ExhaustiveTestHelper2Args(IntegerMultiplicationRun(false, _, _, _, _)); + ExhaustiveTestHelper2NonRegularArgs(IntegerMultiplicationRun(false, _, _, _, _, _)); } operation SquareIExhaustiveTest() : Unit { @@ -16,7 +16,7 @@ namespace Microsoft.Quantum.Numerics.ToffoliTests { } operation DivideIExhaustiveTest() : Unit { - ExhaustiveTestHelper2Args(IntegerDivisionRun); + ExhaustiveTestHelper2RegularArgs(IntegerDivisionRun); } operation SquareSIExhaustiveTest() : Unit { @@ -24,11 +24,11 @@ namespace Microsoft.Quantum.Numerics.ToffoliTests { } operation CompareGTSIExhaustiveTest() : Unit { - ExhaustiveTestHelper2Args(IntegerGreaterThanRun(true, _, _, _, _)); + ExhaustiveTestHelper2RegularArgs(IntegerGreaterThanRun(true, _, _, _, _)); } operation MultiplySIExhaustiveTest() : Unit { - ExhaustiveTestHelper2Args(IntegerMultiplicationRun(true, _, _, _, _)); + ExhaustiveTestHelper2NonRegularArgs(IntegerMultiplicationRun(true, _, _, _, _, _)); } operation ComputeReciprocalIExhaustiveTest() : Unit { @@ -95,63 +95,57 @@ namespace Microsoft.Quantum.Numerics.ToffoliTests { } operation IntegerMultiplicationRun(signed: Bool, a: Int, b: Int, - n: Int, numCtrl: Int) : Unit { - use aqs = Qubit[n]; - use bqs = Qubit[n]; - use cqs = Qubit[2 * n]; + na: Int, nb : Int, numCtrl: Int) : Unit { + let nc = na + nb; + use aqs = Qubit[na]; + use bqs = Qubit[nb]; + use cqs = Qubit[nc]; use ctrlqs = Qubit[numCtrl]; - ApplyXorInPlace(a, LittleEndian(aqs)); - ApplyXorInPlace(b, LittleEndian(bqs)); - if (signed) { - MultiplySI( - SignedLittleEndian(LittleEndian(aqs)), - SignedLittleEndian(LittleEndian(bqs)), - SignedLittleEndian(LittleEndian(cqs))); - } - else { - MultiplyI (LittleEndian(aqs), - LittleEndian(bqs), - LittleEndian(cqs)); - } - mutable asigned = a; - mutable bsigned = b; - if (signed and a >= 2^(n-1)) { - set asigned = -2^n+a; - } - if (signed and b >= 2^(n-1)) { - set bsigned = -2^n+b; + + let aLE = LittleEndian(aqs); + let bLE = LittleEndian(bqs); + let cLE = LittleEndian(cqs); + + ApplyXorInPlace(a, aLE); + ApplyXorInPlace(b, bLE); + if signed { + MultiplySI(SignedLittleEndian(aLE), + SignedLittleEndian(bLE), + SignedLittleEndian(cLE)); + } else { + MultiplyI(aLE, bLE, cLE); } + + let asigned = signed and a >= 2^(na - 1) ? -2^na + a | a; + let bsigned = signed and b >= 2^(nb - 1) ? -2^nb + b | b; mutable c = asigned * bsigned; - mutable cMeasured = MeasureInteger(LittleEndian(cqs)); - if (signed and cMeasured >= 2^(2*n-1)){ - set cMeasured = -2^(2*n) + cMeasured; + mutable cMeasured = MeasureInteger(cLE); + if signed and cMeasured >= 2^(nc-1) { + set cMeasured = -2^nc + cMeasured; } EqualityFactI(c, cMeasured, $"Multiplication did not yield the correct result: - {asigned} * {bsigned} = {c} != {cMeasured} [n={n}]"); + {asigned} * {bsigned} = {c} != {cMeasured} [na={na}, nb={nb}]"); ResetAll(aqs + bqs + cqs); for ctrlState in 0..2^numCtrl - 1 { ApplyXorInPlace(ctrlState, LittleEndian(ctrlqs)); - ApplyXorInPlace(a, LittleEndian(aqs)); - ApplyXorInPlace(b, LittleEndian(bqs)); + ApplyXorInPlace(a, aLE); + ApplyXorInPlace(b, bLE); if signed { - (Controlled MultiplySI) (ctrlqs, - (SignedLittleEndian(LittleEndian(aqs)), - SignedLittleEndian(LittleEndian(bqs)), - SignedLittleEndian(LittleEndian(cqs)))); + Controlled MultiplySI(ctrlqs, + (SignedLittleEndian(aLE), + SignedLittleEndian(bLE), + SignedLittleEndian(cLE))); } else { - (Controlled MultiplyI) (ctrlqs, - (LittleEndian(aqs), - LittleEndian(bqs), - LittleEndian(cqs))); + Controlled MultiplyI(ctrlqs, (aLE, bLE, cLE)); } set c = asigned * bsigned; - if ctrlState != 2^numCtrl-1 { + if ctrlState != 2^numCtrl - 1 { set c = 0; } - set cMeasured = MeasureInteger(LittleEndian(cqs)); - if signed and cMeasured >= 2^(2*n-1) { - set cMeasured = -2^(2*n) + cMeasured; + set cMeasured = MeasureInteger(cLE); + if signed and cMeasured >= 2^(nc - 1) { + set cMeasured = -2^nc + cMeasured; } EqualityFactI(c, cMeasured, "Controlled multiplication did not yield the correct result."); @@ -296,7 +290,7 @@ namespace Microsoft.Quantum.Numerics.ToffoliTests { } } - operation ExhaustiveTestHelper1Arg(TestFunction: ((Int, Int, Int) => Unit)) : Unit { + operation ExhaustiveTestHelper1Arg(TestFunction: (Int, Int, Int) => Unit) : Unit { for numCtrlQubits in 0..2 { for numQubits in 1..5 { for a in 0..2^numQubits - 1 { @@ -306,7 +300,7 @@ namespace Microsoft.Quantum.Numerics.ToffoliTests { } } - operation ExhaustiveTestHelper2Args(TestFunction: ((Int, Int, Int, Int) => Unit)) : Unit { + operation ExhaustiveTestHelper2RegularArgs(TestFunction: (Int, Int, Int, Int) => Unit) : Unit { for numCtrlQubits in 0..2 { for numQubits in 1..5 { for a in 0..2^numQubits - 1 { @@ -317,4 +311,18 @@ namespace Microsoft.Quantum.Numerics.ToffoliTests { } } } -} \ No newline at end of file + + operation ExhaustiveTestHelper2NonRegularArgs(TestFunction: (Int, Int, Int, Int, Int) => Unit) : Unit { + for numCtrlQubits in 0..2 { + for numQubitsA in 1..4 { + for numQubitsB in 1..4 { + for a in 0..2^numQubitsA - 1 { + for b in 0..2^numQubitsB - 1 { + TestFunction(a, b, numQubitsA, numQubitsB, numCtrlQubits); + } + } + } + } + } + } +} From eda8d6216f2e1d6cadaba4c192a10671b8c87a5c Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Sun, 17 Apr 2022 11:18:19 +0200 Subject: [PATCH 2/4] Code refactoring. --- Numerics/src/Integer/Multiplication.qs | 112 ++++++++++++++----------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/Numerics/src/Integer/Multiplication.qs b/Numerics/src/Integer/Multiplication.qs index 62ed46c333c..25fed55c775 100644 --- a/Numerics/src/Integer/Multiplication.qs +++ b/Numerics/src/Integer/Multiplication.qs @@ -13,11 +13,11 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// ๐‘›โ‚-bit multiplicand (LittleEndian) + /// ๐‘›โ‚-bit multiplicand (`LittleEndian`) /// ## ys - /// ๐‘›โ‚‚-bit multiplier (LittleEndian) + /// ๐‘›โ‚‚-bit multiplier (`LittleEndian`) /// ## result - /// (๐‘›โ‚+๐‘›โ‚‚)-bit result (LittleEndian), must be in state |0โŸฉ initially. + /// (๐‘›โ‚+๐‘›โ‚‚)-bit result (`LittleEndian`), must be in state |0โŸฉ initially. /// /// # Remarks /// Uses a standard shift-and-add approach to implement the multiplication. @@ -98,32 +98,50 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// $n$-bit number to square (LittleEndian) + /// ๐‘›-bit number to square (`LittleEndian`) /// ## result - /// $2n$-bit result (LittleEndian), must be in state $\ket{0}$ initially. + /// 2๐‘›-bit result (`LittleEndian`), must be in state |0โŸฉ initially. /// /// # Remarks /// Uses a standard shift-and-add approach to compute the square. Saves - /// $n-1$ qubits compared to the straight-forward solution which first - /// copies out xs before applying a regular multiplier and then undoing + /// ๐‘›-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: LittleEndian, result: LittleEndian) : Unit { + operation SquareI(xs: LittleEndian, result: LittleEndian) : Unit { body (...) { Controlled SquareI([], (xs, result)); } controlled (controls, ...) { let n = Length(xs!); - EqualityFactI(2 * n, Length(result!), "Integer multiplication - requires a 2n-bit result registers."); + EqualityFactI(2 * n, Length(result!), "Integer multiplication requires a 2n-bit result registers."); AssertAllZero(result!); - use aux = Qubit(); - for i in 0..n - 1 { - (Controlled CNOT) (controls, (xs![i], aux)); - (Controlled AddI) ([aux], (xs, - LittleEndian(result![i..i+n]))); - (Controlled CNOT) (controls, (xs![i], aux)); + let numControls = Length(controls); + if numControls == 0 { + SquareI(xs, result); + } elif numControls == 1 { + use aux = Qubit(); + for (idx, ctl) in Enumerated(xs!) { + within { + ApplyAnd(controls[0], ctl, aux); + } apply { + Controlled AddI([aux], (xs, LittleEndian(result![idx..idx + n]))); + } + } + } else { + use helper = Qubit[numControls]; + within { + ApplyAndLadder(controls, Most(helper)); + } apply { + for (idx, ctl) in Enumerated(xs!) { + within { + ApplyAnd(Tail(Most(helper)), ctl, Tail(helper)); + } apply { + Controlled AddI([Tail(helper)], (xs, LittleEndian(result![idx..idx + n]))); + } + } + } } } adjoint auto; @@ -136,15 +154,13 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// n-bit multiplicand (SignedLittleEndian) + /// ๐‘›โ‚-bit multiplicand (`SignedLittleEndian`) /// ## ys - /// n-bit multiplier (SignedLittleEndian) + /// ๐‘›โ‚‚-bit multiplier (`SignedLittleEndian`) /// ## result - /// 2n-bit result (SignedLittleEndian), must be in state $\ket{0}$ + /// (๐‘›โ‚+๐‘›โ‚‚)-bit result (`SignedLittleEndian`), must be in state |0โŸฉ /// initially. - operation MultiplySI (xs: SignedLittleEndian, - ys: SignedLittleEndian, - result: SignedLittleEndian): Unit { + operation MultiplySI(xs: SignedLittleEndian, ys: SignedLittleEndian, result: SignedLittleEndian): Unit { body (...) { Controlled MultiplySI([], (xs, ys, result)); } @@ -152,22 +168,21 @@ namespace Microsoft.Quantum.Arithmetic { use signx = Qubit(); use signy = Qubit(); - CNOT(Tail(xs!!), signx); - CNOT(Tail(ys!!), signy); - (Controlled Invert2sSI)([signx], xs); - (Controlled Invert2sSI)([signy], ys); - - (Controlled MultiplyI) (controls, (xs!, ys!, result!)); - CNOT(signx, signy); - // No controls required since `result` will still be zero - // if we did not perform the multiplication above. - (Controlled Invert2sSI)([signy], result); - CNOT(signx, signy); - - (Controlled Adjoint Invert2sSI)([signx], xs); - (Controlled Adjoint Invert2sSI)([signy], ys); - CNOT(Tail(xs!!), signx); - CNOT(Tail(ys!!), signy); + 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); + } + } } adjoint auto; adjoint controlled auto; @@ -179,15 +194,14 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// n-bit integer to square (SignedLittleEndian) + /// ๐‘›-bit integer to square (`SignedLittleEndian`) /// ## result - /// 2n-bit result (SignedLittleEndian), must be in state $\ket{0}$ + /// 2๐‘›-bit result (`SignedLittleEndian`), must be in state |0โŸฉ /// initially. /// /// # Remarks - /// The implementation relies on IntegerSquare. - operation SquareSI (xs: SignedLittleEndian, - result: SignedLittleEndian): Unit is Adj + Ctl { + /// The implementation relies on `SquareI`. + operation SquareSI (xs: SignedLittleEndian, result: SignedLittleEndian): Unit is Adj + Ctl { body (...) { Controlled SquareSI([], (xs, result)); } @@ -195,13 +209,13 @@ namespace Microsoft.Quantum.Arithmetic { let n = Length(xs!!); use signx = Qubit(); use signy = Qubit(); - CNOT(Tail(xs!!), signx); - (Controlled Invert2sSI)([signx], xs); - - (Controlled SquareI) (controls, (xs!, result!)); - (Controlled Adjoint Invert2sSI)([signx], xs); - CNOT(Tail(xs!!), signx); + within { + CNOT(Tail(xs!!), signx); + Controlled Invert2sSI([signx], xs); + } apply { + Controlled SquareI(controls, (xs!, result!)); + } } } } From 843537f420f8a9025a44f9664c142e3ffd433b3b Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Sun, 17 Apr 2022 12:09:32 +0200 Subject: [PATCH 3/4] Fix recursion error. --- Numerics/src/Integer/Multiplication.qs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Numerics/src/Integer/Multiplication.qs b/Numerics/src/Integer/Multiplication.qs index 25fed55c775..656066d2675 100644 --- a/Numerics/src/Integer/Multiplication.qs +++ b/Numerics/src/Integer/Multiplication.qs @@ -119,7 +119,14 @@ namespace Microsoft.Quantum.Arithmetic { let numControls = Length(controls); if numControls == 0 { - SquareI(xs, result); + use aux = Qubit(); + for (idx, ctl) in Enumerated(xs!) { + within { + CNOT(ctl, aux); + } apply { + Controlled AddI([aux], (xs, LittleEndian(result![idx..idx + n]))); + } + } } elif numControls == 1 { use aux = Qubit(); for (idx, ctl) in Enumerated(xs!) { From d6cc993b277e50ff031d5dfd801a371957a5e6a3 Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Wed, 20 Apr 2022 11:39:53 -0700 Subject: [PATCH 4/4] Adress reviewer comments. --- Numerics/src/Integer/Multiplication.qs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Numerics/src/Integer/Multiplication.qs b/Numerics/src/Integer/Multiplication.qs index 656066d2675..4b5d7a7f5ae 100644 --- a/Numerics/src/Integer/Multiplication.qs +++ b/Numerics/src/Integer/Multiplication.qs @@ -13,11 +13,11 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// ๐‘›โ‚-bit multiplicand (`LittleEndian`) + /// ๐‘›โ‚-bit multiplicand /// ## ys - /// ๐‘›โ‚‚-bit multiplier (`LittleEndian`) + /// ๐‘›โ‚‚-bit multiplier /// ## result - /// (๐‘›โ‚+๐‘›โ‚‚)-bit result (`LittleEndian`), must be in state |0โŸฉ initially. + /// (๐‘›โ‚+๐‘›โ‚‚)-bit result, must be in state |0โŸฉ initially. /// /// # Remarks /// Uses a standard shift-and-add approach to implement the multiplication. @@ -98,9 +98,9 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// ๐‘›-bit number to square (`LittleEndian`) + /// ๐‘›-bit number to square /// ## result - /// 2๐‘›-bit result (`LittleEndian`), must be in state |0โŸฉ initially. + /// 2๐‘›-bit result, must be in state |0โŸฉ initially. /// /// # Remarks /// Uses a standard shift-and-add approach to compute the square. Saves @@ -161,11 +161,11 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// ๐‘›โ‚-bit multiplicand (`SignedLittleEndian`) + /// ๐‘›โ‚-bit multiplicand /// ## ys - /// ๐‘›โ‚‚-bit multiplier (`SignedLittleEndian`) + /// ๐‘›โ‚‚-bit multiplier /// ## result - /// (๐‘›โ‚+๐‘›โ‚‚)-bit result (`SignedLittleEndian`), must be in state |0โŸฉ + /// (๐‘›โ‚+๐‘›โ‚‚)-bit result, must be in state |0โŸฉ /// initially. operation MultiplySI(xs: SignedLittleEndian, ys: SignedLittleEndian, result: SignedLittleEndian): Unit { body (...) { @@ -201,9 +201,9 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// ๐‘›-bit integer to square (`SignedLittleEndian`) + /// ๐‘›-bit integer to square /// ## result - /// 2๐‘›-bit result (`SignedLittleEndian`), must be in state |0โŸฉ + /// 2๐‘›-bit result, must be in state |0โŸฉ /// initially. /// /// # Remarks