diff --git a/Numerics/src/Integer/Multiplication.qs b/Numerics/src/Integer/Multiplication.qs index 01383d19bfc..4b5d7a7f5ae 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,82 +13,142 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// $n$-bit multiplicand (LittleEndian) + /// ๐‘›โ‚-bit multiplicand /// ## ys - /// $n$-bit multiplier (LittleEndian) + /// ๐‘›โ‚‚-bit multiplier /// ## result - /// $2n$-bit result (LittleEndian), must be in state $\ket{0}$ initially. + /// (๐‘›โ‚+๐‘›โ‚‚)-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 $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. /// /// # Input /// ## xs - /// $n$-bit number to square (LittleEndian) + /// ๐‘›-bit number to square /// ## result - /// $2n$-bit result (LittleEndian), must be in state $\ket{0}$ initially. + /// 2๐‘›-bit result, 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 { + 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!) { + 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; @@ -100,39 +161,35 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// n-bit multiplicand (SignedLittleEndian) + /// ๐‘›โ‚-bit multiplicand /// ## ys - /// n-bit multiplier (SignedLittleEndian) + /// ๐‘›โ‚‚-bit multiplier /// ## result - /// 2n-bit result (SignedLittleEndian), must be in state $\ket{0}$ + /// (๐‘›โ‚+๐‘›โ‚‚)-bit result, 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)); } controlled (controls, ...) { - let n = Length(xs!!); 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; @@ -144,15 +201,14 @@ namespace Microsoft.Quantum.Arithmetic { /// /// # Input /// ## xs - /// n-bit integer to square (SignedLittleEndian) + /// ๐‘›-bit integer to square /// ## result - /// 2n-bit result (SignedLittleEndian), must be in state $\ket{0}$ + /// 2๐‘›-bit result, 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)); } @@ -160,13 +216,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!)); + } } } } diff --git a/Numerics/tests/IntegerHighLevelTests.qs b/Numerics/tests/IntegerHighLevelTests.qs index 9950eb50937..be4a64dff5b 100644 --- a/Numerics/tests/IntegerHighLevelTests.qs +++ b/Numerics/tests/IntegerHighLevelTests.qs @@ -9,7 +9,7 @@ namespace Microsoft.Quantum.Tests { @Test("ToffoliSimulator") operation MultiplyIExhaustiveTest() : Unit { - ExhaustiveTestHelper2Args(IntegerMultiplicationRun(false, _, _, _, _)); + ExhaustiveTestHelper2NonRegularArgs(IntegerMultiplicationRun(false, _, _, _, _, _)); } @Test("ToffoliSimulator") @@ -19,7 +19,7 @@ namespace Microsoft.Quantum.Tests { @Test("ToffoliSimulator") operation DivideIExhaustiveTest() : Unit { - ExhaustiveTestHelper2Args(IntegerDivisionRun); + ExhaustiveTestHelper2RegularArgs(IntegerDivisionRun); } @Test("ToffoliSimulator") @@ -29,12 +29,12 @@ namespace Microsoft.Quantum.Tests { @Test("ToffoliSimulator") operation CompareGTSIExhaustiveTest() : Unit { - ExhaustiveTestHelper2Args(IntegerGreaterThanRun(true, _, _, _, _)); + ExhaustiveTestHelper2RegularArgs(IntegerGreaterThanRun(true, _, _, _, _)); } @Test("ToffoliSimulator") operation MultiplySIExhaustiveTest() : Unit { - ExhaustiveTestHelper2Args(IntegerMultiplicationRun(true, _, _, _, _)); + ExhaustiveTestHelper2NonRegularArgs(IntegerMultiplicationRun(true, _, _, _, _, _)); } @Test("ToffoliSimulator") @@ -102,63 +102,57 @@ namespace Microsoft.Quantum.Tests { } internal 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."); @@ -303,7 +297,7 @@ namespace Microsoft.Quantum.Tests { } } - internal operation ExhaustiveTestHelper1Arg(TestFunction: ((Int, Int, Int) => Unit)) : Unit { + internal 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 { @@ -313,7 +307,8 @@ namespace Microsoft.Quantum.Tests { } } - internal operation ExhaustiveTestHelper2Args(TestFunction: ((Int, Int, Int, Int) => Unit)) : Unit { + // Tests an operation that expects two input arguments with the same number of bits + internal 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 { @@ -324,4 +319,19 @@ namespace Microsoft.Quantum.Tests { } } } -} \ No newline at end of file + + // Tests an operation that expects two input arguments with a different number of bits + internal 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); + } + } + } + } + } + } +}