From 45a80e9ebb77fefef79c4371574dd5fcd7df18b8 Mon Sep 17 00:00:00 2001 From: "Krishnakumar, Rajiv [Engineering]" Date: Wed, 3 Aug 2022 15:30:46 -0400 Subject: [PATCH 01/17] lookup table code --- Numerics/src/FixedPoint/LookupTable.qs | 342 +++++++++++++++++++++++++ Numerics/tests/LookupTableTests.qs | 98 +++++++ 2 files changed, 440 insertions(+) create mode 100755 Numerics/src/FixedPoint/LookupTable.qs create mode 100755 Numerics/tests/LookupTableTests.qs diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs new file mode 100755 index 00000000000..2186cfbdda8 --- /dev/null +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -0,0 +1,342 @@ +namespace Microsoft.Quantum.Arithmetic { + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Math; + + /// # Summary + /// The return type when making a lookup table. This contains the operation that + /// makes the lookup table circuit, as well as all the parameters required to make + /// the two FixedPoint registers that need to be used as inputs and outputs to the + /// operator. The reason we have this structure is so that the operator is similar + /// to the other typical Q# arithmetic function implementations (a larger discussion + /// can had as to whether that can be changed) + newtype MakeLookupReturn = ( + IntegerBitsIn: Int, + FractionalBitsIn: Int, + IntegerBitsOut: Int, + FractionalBitsOut: Int, + Apply: (FixedPoint, FixedPoint) => Unit is Adj + ); + + /// # Summary + /// This function creates a select-swap lookup table operator for the function that you want to approximate, as well as + /// the parameters required to make the two FixedPoint registers that need to be used as inputs to the operator. + /// This is so that it is in similar to the other typical Q# arithmetic function (a larger discussion can be had + /// as to whether that can be changed). The circuit for the operator can be found in Fig. 1c in arXiv:1812.00954. + /// The operator guarantees that given an input value x and a function f(x), + /// it will compute f'(x') where f' is an approximation of f with a maximum error of epsOut and x' is an + /// approximation of the input value x with a maximum error of epsIn. This is useful for most reasonably behaved + /// functions, but not that it computes f'(x') and not f'(x) so if the domain function is very oscillatory and/or + /// has funky derivatives then it may have high errors. + /// + /// # Input + /// ## func + /// The Q# arithmetic function that you want to implement with the lookup table + /// ## domain + /// A tuple consisting of the minimum and maximum values of the input values to the function + /// ## epsIn + /// The maximum allowed error of the input value to the computation (i.e. |x'-x|) + /// ## epsOut + /// The maximum allowed error of the output without taking into account the error in input value (i.e. |f'(x')-f(x')|) + /// ## numSwapBits + /// The number of bits of the input register that will be used in the SWAP section of the circuits. Another way of looking + /// at this is that in step in the SELECT section of the circuit in Fig 1c of arXiv:1812.00954, we will encode 2^numSawpBits + /// encoded + function MakeLookup(func: Double -> Double, domain: (Double, Double), epsIn: Double, epsOut: Double, numSwapBits: Int): MakeLookupReturn { + + // First step is to find the number of integer bits (pIn) and fractional bits (qIn) required for the input based on the + // domain and error tolerance (espIn). To find the value of pIn, we have to check both the + // lower and upper bound of the domain to see which one requires more bits, then assign the larger one as pIn. + // To find qIn we compute minimum number of fractional bits required to represent epsIn. + let (minIn, maxIn) = domain; + + let pLower = BitSizeI(Ceiling(AbsD(minIn))); + let pUpper = BitSizeI(Ceiling(AbsD(maxIn))); + let pIn = MaxI(pLower, pUpper) + 1; // The +1 is for the sign bit + + let qIn = Ceiling(Lg(1.0/epsIn)); + + // We have now computed the number of integer and fractional bits required for the input of our lookup table. Next we compute + // The output number of integer and fractional bits required. For the number of fractional bits (qOut), we can + // simply look at the minimum number of bits required to represent epsOut + let qOut = Ceiling(Lg(1.0/epsOut)); + + + // For the number of integer bits required for the output, we have to iterate through all the possible values of the function + // and find the one with the largest absolute value. For that we first create the fixed point approximations of minIn and maxIn + // given epsIn (using the previously computed pIn and qIn). Then we compute how many different input values (numValues) are there between + // minIn and maxIn (given our number of input qubits). And finally we evaluate the function at all those values to get the number with + // the largest absolute value + + // Compute approximations of minIn and maxIn + let minInFxP = DoubleAsFixedPoint(pIn, qIn, minIn); + let maxInFxP = DoubleAsFixedPoint(pIn, qIn, maxIn); + + // Compute number of values in between minIn and maxIn + let deltaIn = 1.0/PowD(2.0, IntAsDouble(qIn)); + let numValues = Truncate((maxInFxP - minInFxP) / deltaIn) + 1; + + // Go through each value, compute the number of integer bits required, and update pOut if it's bigger than + // current pOut. We also store the output values since we will be using them when creating the output part of the + // lookup table circuit + mutable outValues = [0.0, size=numValues]; // List to store all the output values (initialized at all 0s) + mutable inValueFxP = minInFxP; // Starting input value + mutable pOut = 0; // Set initial pOut value which will be updated in loop below + for i in 0..numValues-1 { + // First a quick check to see that the enumaration is going correctly, i.e. that we are hitting all the values in order + let inAddress = BoolArrayAsInt(FixedPointAsBoolArray(pIn, qIn, inValueFxP - minInFxP)); + EqualityFactI(inAddress, i, $"Unexpected address in enumeration"); + + // Now we compute the output value, compute the number of integer bits it has and see if it is bigger than our current pOut + let outValue = func(inValueFxP); + set outValues w/= i <- outValue; // this is the syntax to say "outValues = outValues but with the ith index as outValue" + set pOut = MaxI(pOut, BitSizeI(Ceiling(AbsD(outValue)))+1); //the +1 is for the sign bit + set inValueFxP += deltaIn; + } + + //So we have now computed the number of integer bits for the output values. Now all that's left is to make the circuit! + + // We first create a list of FixedPoints with all the outValues + let outValuesFxP = Mapped(DoubleAsFixedPoint(pOut, qOut, _), outValues); + + // Next we map outValuesFP to bitstrings + let outBits = Mapped(FixedPointAsBoolArray(pOut, qOut, _), outValues); + // Message($"{outBits}"); + + // Now we use the fixed point approximation of the minimum value of the input + // and the list of output bit values to make the operation lookupOperation: (FixedPoint, FixedPoint) => Unit + // More comments on how that's done in within the function + let lookupOperation = LookupOperationWrapper(minInFxP, outBits, numSwapBits, _, _); + + + return MakeLookupReturn( + pIn, qIn, + pOut, qOut, + lookupOperation + ); + } + + /// # Summary + /// Creates a lookup table operation. This operation will require the minimum input value as a FixedPoint register, + /// the list of output values in bits,the FixedPoint register with the input value and the FixedPoint register that + /// will store the output value. Note that this imples that the bit size requirement of these registers are pre-computed + /// beforehand + /// + /// # Input + /// ## minInFxp + /// The minimum possible value of the input to the lookup table + /// ## outBits + /// The list of output values in bits in order, where the first value is the output for the smallest input value and + /// the last value is the output for the largest input value + /// ## numSwapBits + /// The number of bits of the input register that will be used in the SWAP section of the circuits. Another way of looking + /// at this is that in step in the SELECT section of the circuit in Fig 1c of arXiv:1812.00954, we will encode 2^numSawpBits + /// encoded + /// ## input + /// Qubit FixedPoint register containing input values + /// ## output + /// Qubit FixedPoint register containing where output values will be stored + /// + /// # Potential improvements TODO + /// fanout inner and outer controls + /// make version with garbage + internal operation LookupOperationWrapper(minInFxP: Double, outBits: Bool[][], numSwapBits : Int, input: FixedPoint, output: FixedPoint) : Unit is Adj { + + let integerBitsIn = input::IntegerBits; + let registerIn = input::Register; + let fractionalBitsIn = Length(registerIn) - integerBitsIn; + + // We are now creating the lookup table. If the smallest value (i.e. minInFxP) happens to be 0, then we can just use + // the Select operation which implements the lookup table in ##. However, if the minimum value is not 0, then we want to first subtract + // it, because the lookup table always assumes that the miminum value is 00...0 and the maximum value is 11...1 in incrementing order, + // so we are re-defining the minimum number as represented by 00...0 and hence subracting the minimum from our value. + // (We add the minimum back after making the lookup table) + within { + if minInFxP != 0.0 { + // Make a new fixed point register to store the minimum vlaue + use minRegister = Qubit[Length(registerIn)]; + let minInReg = FixedPoint(integerBitsIn, minRegister); // + within { + PrepareFxP(minInFxP, minInReg); // Store minimum value in prepared register (automatically creates closest FxP approximation) + } apply { + SubtractFxP(input, minInReg); // SubtractFxP(a, b) : a <- a - b + } + } + } apply { + SelectSwap(numSwapBits, outBits, input::Register, output::Register); + } + } + + /// # Summary + /// Basically applies the ApplyPauliFromBitString function but with the extra check that the length of bits is the same + /// as the number of qubits (so nothing can be implicitly ignored) + internal operation WriteBits(bits: Bool[], qubitArray: Qubit[]): Unit is Adj + Ctl { + EqualityFactI(Length(bits), Length(qubitArray), "Dimensions of bits and qubitArray should be the same"); + ApplyPauliFromBitString(PauliX, true, bits, qubitArray); + } + + /// # Summary + /// Helper function that creates an operator that takes in just 1 binary value input (i.e. a list + /// of booleans) and makes the circuit to apply paulis to create that binary value. We do this + /// so that we can use it as part of the Mapped function to be able to make a list of unitaries + /// given a list of binary numbers + internal function MakeWriteBitsUnitary(bits : Bool[]) : Qubit[] => Unit is Adj + Ctl { + return WriteBits(bits, _); + + } + + /// # Summary + /// This opration makes the lookup table by using the multiplex unitary operator - the operator that implements + /// different unitaries based on the value of the controlled bits. We just define each unitary as the set of + /// PauliX gates that will make the output qubit correspond to the data bits. + internal operation Select(data : Bool[][], addressRegister: Qubit[], outputRegister: Qubit[]) : Unit is Adj { + + let unitaries = Mapped(MakeWriteBitsUnitary, data); + MultiplexOperations(unitaries, LittleEndian(addressRegister), outputRegister); + // MultiplexOperationsWithUnaryIteration(unitaries, LittleEndian(addressRegister), outputRegister); + } + + /// # Summary + /// This operation makes the swap circuit. The outputRegisters are 2^l qubit registers, each of size m + internal operation SwapDataOutputs(addressRegister: Qubit[], outputRegisters: Qubit[][]) : Unit is Adj { + let l = Length(addressRegister); + // For each input qubit we are using for swap qubits, we want to implement all the swaps + for i in 0.. Length(addressRegister)-1{ + // for the ith input qubit, we need to have to swap qubit registers that are 2^i places apart, and we need to do this for every qubit that is 2^(i+1) qubits apart, + // and we need to swap atotal of 2^l/2^(i+1) registers, where l is the number of output registers. E.g. + // i=0 => swaps between registers (0,1), (2,3), (4,5),..., (2^l - 2, 2^l - 1) + // i=1 => swaps between registers (0,2), (4,6), (8,10),..., (2^l - 4, 2^l - 2) + // i=2 => swaps between registers (0,2^i), (2^(i+1), 2^(i+1) + 2^i),..., (2^l - 2^(i+1), 2^l - 2^(i+1) + 2^i) + let innerStepSize = 2^i; + let outerStepSize = 2^(i+1); + let numSwaps = 2^l/2^(i+1); + use extraControls = Qubit[numSwaps-1]; + let fannedControls = [addressRegister[i]] + extraControls; + within { + ApplyToEachA(CNOT(addressRegister[i],_), extraControls); // Fanning out qubits to be able to do a fanned control + } apply { + for j in 0.. numSwaps-1{ + ApplyMultiTargetSwap(fannedControls[j], outputRegisters[j*outerStepSize], outputRegisters[j*outerStepSize+innerStepSize]); + } + } + } + } + + /// # Summary + /// Impelements an efficient control swap (with 2 CNOTs and one CCNOT) + internal operation ApplyLowDepthCSWAP(control : Qubit, target1 : Qubit, target2 : Qubit) : Unit is Adj { + use helper = Qubit(); + + within { + CNOT(target2, target1); + ApplyLowDepthAnd(control, target1, helper); // this has T-depth 1, AND = CCNOT where target is in |0> + } apply { + CNOT(helper, target2); + } + } + + /// # Summary + /// Implements a control swap two registers controlled on a single qubits. To be able to parallelize it, it will + /// fan out the control register and perform all the swaps in parallel + internal operation ApplyMultiTargetSwap(control : Qubit, target1 : Qubit[], target2 : Qubit[]) : Unit is Adj { + EqualityFactI(Length(target1), Length(target2), "The two qubit registers are of different sizes"); + + use extraControls = Qubit[Length(target1) - 1]; + let fannedControls = [control] + extraControls; + + within { + ApplyToEachA(CNOT(control, _), extraControls); + } apply { + for i in 0..Length(target1)-1{ + ApplyLowDepthCSWAP(fannedControls[i], target1[i], target2[i]); + } + } + } + + /// # Summary + /// Creates the select-swap circuit. Uses the most significant bits of `addressRegister` to use for SWAP network. + /// Let n be `Length(addressRegister)`, the number of address bits, and let l be `numSwapBits`. Then the T-depth is + /// approximately O(2^{(n-l}) + l. If we want m output qubits, the number of additional ancilla qubits is m * 2^l: + /// These due additional ancilla qubits come from the swap + /// + /// # Input + /// ## numSwapBits + /// The number of bits of the input register that will be used in the SWAP section of the circuits. Another way of looking + /// at this is that in step in the SELECT section of the circuit in Fig 1c of arXiv:1812.00954, we will encode 2^numSawpBits + /// encoded. + /// ## data + /// The list of output values in bits in order, where the first value is the output for the value 00...0 and + /// the last value is the output for the value the largest integer value + /// ## addressRegister + /// Input register of qubits + /// ## outputRegister + /// Output register where the output values will be stored + internal operation SelectSwap(numSwapBits : Int, data : Bool[][], addressRegister: Qubit[], outputRegister: Qubit[]) : Unit is Adj{ + + let n = Length(addressRegister); + + // how many address bits do we need for `data`? We do this so that we optimize if Length(data) <= 2^(n-1) and don't have to use all the input qubits in the swap register + let nRequired = Ceiling(Lg(IntAsDouble(Length(data)))); + Fact(nRequired <= n, "Too few address bits"); + let addressRegisterFitted = addressRegister[...nRequired - 1]; + + // Probably numSwapBits == nRequired works, but has to be checked + Fact(numSwapBits < nRequired, "Too many bits for SWAP network"); + + if numSwapBits == 0 { // to not uncompute select if l=0 + Select(data, addressRegisterFitted, outputRegister); + } else { + let m = Length(outputRegister); + // number of SWAP bits + let l = numSwapBits; + // number of SELECT bits + let k = nRequired - numSwapBits; + + let addressRegisterParts = Partitioned([k, l], addressRegisterFitted); //creates two parts of the array - 1 for the select and 1 for the swaps + + use dataRegister = Qubit[m * 2^l]; //create one register with m*2^l qubits to have all the outputs stored + + // Now we want to create the data array for the select bit. This means that for the first array, we want all the outputs corresponding to the most sig bits = 00..00, then for the next array 00..01, then 00..10 etc. + let dataArray = CreatePaddedData(data, nRequired, m, k); + + // Now we split up the register in to chunks of m + let chunkedDataRegister = Chunks(m, dataRegister); //Chunks(nElements, array): splits array into chunks, where each chunk has nElements, e.g. Chunks(2, [1, 2, 3, 4, 5, 6]) -> [[1, 2], [3, 4], [5, 6]] + + // Perform select swap with the (1) dataArray for the select outputs and (2) chunkedDataRegister for the swap targets + within { + Select(dataArray, addressRegisterParts[0], dataRegister); // apply select using first part of address registers + SwapDataOutputs(addressRegisterParts[1], chunkedDataRegister); // apply swap using second part of address registers + } apply { + ApplyToEachA(CNOT, Zipped(chunkedDataRegister[0], outputRegister)); // apply CNOT from chunkedDataRegister[0] to outputRegister + } + } + } + + /// # Summary + /// Helper function that creates a list of binary numbers were each number is a concatination of all the possible outputs of each + /// select function (i.e. combining all the possible outputs into a single bitstring which will then be used in the swap bit of the + /// select-swap) + /// + /// # Input + /// ## data + /// The list of output values in bits in order, where the first value is the output for the value 00...0 and + /// the last value is the output for the value the largest integer value + /// ## nRequired + /// Minimum number of address bits required to ensure the largest data point can be stored + /// ## m + /// Size of final single output of lookup table + /// ## k + /// Number of bits from input that will be used in the select part + internal function CreatePaddedData(data : Bool[][], nRequired : Int, m : Int, k : Int) : Bool[][] { + let dataPadded = Padded(-2^nRequired, [false, size = m], data); // Padded so that we don't have problems for powers of 2 + mutable dataArray = [[], size = 2^k]; + for i in 0..2^k-1 { + let range = RangeAsIntArray(i..2^k..2^nRequired-1); // RangeAsIntArray(range) takes a Range and returns Int[], e.g., 1..3 -> [1, 2, 3], 1..3..9 -> [1, 4, 7]. In our case, for i=0 -> [0, 2^k, 2*2^k...] + set dataArray w/= i <- Flattened(Subarray(range, dataPadded)); // Subarray(indices, array) takes indices Int[] and returns subarray of array subject to indices + } + return dataArray; + } +} diff --git a/Numerics/tests/LookupTableTests.qs b/Numerics/tests/LookupTableTests.qs new file mode 100755 index 00000000000..7ccead84273 --- /dev/null +++ b/Numerics/tests/LookupTableTests.qs @@ -0,0 +1,98 @@ +namespace Microsoft.Quantum.Tests { + open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Random; + + /// # Summary + /// Example of exponential operation with [0, 9.23] with input-eps 1e-3, output-eps 1e-3 + /// Input should require something like 5 integer bits and 10 fractional bits + /// Output should require something like 15 integer bits and 10 fractional bits + operation ExponentialExample(numSwapBits: Int, input : FixedPoint, output : FixedPoint) : Unit { + let func = ExpD; + let xMin = 0.0; + let xMax = 9.23; + let epsIn = 1e-3; + let epsOut = 1e-3; + + let lookup = MakeLookup(ExpD, (xMin, xMax), epsIn, epsOut, numSwapBits); + // Check that input and output registers are the expected size + EqualityFactI(lookup::IntegerBitsIn + lookup::FractionalBitsIn, 15, "Number of input bits is incorrect"); + EqualityFactI(lookup::IntegerBitsOut + lookup::FractionalBitsOut, 25, "Number of output bits is incorrect"); + lookup::Apply(input, output); + } + + /// # Summary + /// Example to call Exponential with pre-computed number of input and + /// output qubits from ExponentialExample + operation EstimateExponentialInstance(numSwapBits : Int) : Unit { + use input = Qubit[15]; + use output = Qubit[25]; + let inputFxP = FixedPoint(5, input); + let outputFxP = FixedPoint(15, output); + + ExponentialExample(numSwapBits, inputFxP, outputFxP); + } + + //Tests for some examples + @Test("ToffoliSimulator") + operation TestExponentialExample() : Unit { + use input = Qubit[15]; + use output = Qubit[25]; + let inputFxP = FixedPoint(5, input); + let outputFxP = FixedPoint(15, output); + + for i in 0..9 { + let inputValue = DrawRandomDouble(9.23*IntAsDouble(i)/10.0, 9.23*IntAsDouble(i+1)/10.0); + + PrepareFxP(inputValue, inputFxP); + ExponentialExample(5, inputFxP, outputFxP); + + let inResult = MeasureFxP(inputFxP); + let outResult = MeasureFxP(outputFxP); + + let expected = ExpD(inResult); + + EqualityWithinToleranceFact(inResult, inputValue, 1e-3); + EqualityWithinToleranceFact(outResult, expected, 1e-3); + + Message($"{inResult} {outResult}"); + } + } + + @Test("ToffoliSimulator") + operation TestFxPRegisterValues(): Unit { + let func = ExpD; + let xMin = -1.0; + let xMax = 8.125; + let epsIn = 0.125; + let epsOut = 0.25; + + let lookup = MakeLookup(func, (xMin, xMax), epsIn, epsOut, 0); + + use inputRegister = Qubit[lookup::IntegerBitsIn+lookup::FractionalBitsIn]; + use outputRegister = Qubit[lookup::IntegerBitsOut+lookup::FractionalBitsOut]; + + let inputFxP = FixedPoint(lookup::IntegerBitsIn, inputRegister); + let outputFxP = FixedPoint(lookup::IntegerBitsOut, outputRegister); + + let samplePoints = 20; + + for i in 0..samplePoints { + let x = xMin + IntAsDouble(i) * (xMax - xMin) / IntAsDouble(samplePoints); + PrepareFxP(x, inputFxP); + lookup::Apply(inputFxP, outputFxP); + let inputResult = MeasureFxP(inputFxP); + let outputResult = MeasureFxP(outputFxP); + + EqualityWithinToleranceFact(inputResult, x, epsIn); + EqualityWithinToleranceFact(outputResult, func(inputResult), epsOut); + + Message($"Input value: {inputResult}, Output value:{outputResult}"); + } + } +} \ No newline at end of file From 3165786c38d777f5447ba094e1aac103f602a862 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Fri, 19 Aug 2022 15:56:03 +0200 Subject: [PATCH 02/17] Update Numerics/tests/LookupTableTests.qs Co-authored-by: Mathias Soeken --- Numerics/tests/LookupTableTests.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Numerics/tests/LookupTableTests.qs b/Numerics/tests/LookupTableTests.qs index 7ccead84273..188a83c36aa 100755 --- a/Numerics/tests/LookupTableTests.qs +++ b/Numerics/tests/LookupTableTests.qs @@ -72,7 +72,7 @@ namespace Microsoft.Quantum.Tests { let epsIn = 0.125; let epsOut = 0.25; - let lookup = MakeLookup(func, (xMin, xMax), epsIn, epsOut, 0); + let lookup = FunctionWithLookupTable(func, (xMin, xMax), epsIn, epsOut, 0); use inputRegister = Qubit[lookup::IntegerBitsIn+lookup::FractionalBitsIn]; use outputRegister = Qubit[lookup::IntegerBitsOut+lookup::FractionalBitsOut]; From fc1a17fb7a1474daff0af3e3278961716161d6d1 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Fri, 19 Aug 2022 15:56:12 +0200 Subject: [PATCH 03/17] Update Numerics/src/FixedPoint/LookupTable.qs Co-authored-by: Mathias Soeken --- Numerics/src/FixedPoint/LookupTable.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs index 2186cfbdda8..049ec66a6fc 100755 --- a/Numerics/src/FixedPoint/LookupTable.qs +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -13,7 +13,7 @@ namespace Microsoft.Quantum.Arithmetic { /// operator. The reason we have this structure is so that the operator is similar /// to the other typical Q# arithmetic function implementations (a larger discussion /// can had as to whether that can be changed) - newtype MakeLookupReturn = ( + newtype FunctionWithLookupTable = ( IntegerBitsIn: Int, FractionalBitsIn: Int, IntegerBitsOut: Int, From 36df890e6d56c74baf7fcb55290e55e9e14bd11a Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Fri, 19 Aug 2022 15:56:21 +0200 Subject: [PATCH 04/17] Update Numerics/src/FixedPoint/LookupTable.qs Co-authored-by: Mathias Soeken --- Numerics/src/FixedPoint/LookupTable.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs index 049ec66a6fc..15e59a0d54a 100755 --- a/Numerics/src/FixedPoint/LookupTable.qs +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -45,7 +45,7 @@ namespace Microsoft.Quantum.Arithmetic { /// The number of bits of the input register that will be used in the SWAP section of the circuits. Another way of looking /// at this is that in step in the SELECT section of the circuit in Fig 1c of arXiv:1812.00954, we will encode 2^numSawpBits /// encoded - function MakeLookup(func: Double -> Double, domain: (Double, Double), epsIn: Double, epsOut: Double, numSwapBits: Int): MakeLookupReturn { + function ApplyFunctionWithLookupTable(func: Double -> Double, domain: (Double, Double), epsIn: Double, epsOut: Double, numSwapBits: Int): FunctionWithLookupTable { // First step is to find the number of integer bits (pIn) and fractional bits (qIn) required for the input based on the // domain and error tolerance (espIn). To find the value of pIn, we have to check both the From 7519eb622bcca016ecd2c11b0c0800befdd508fe Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Fri, 19 Aug 2022 15:56:32 +0200 Subject: [PATCH 05/17] Update Numerics/src/FixedPoint/LookupTable.qs Co-authored-by: Mathias Soeken --- Numerics/src/FixedPoint/LookupTable.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs index 15e59a0d54a..60dae13f5b9 100755 --- a/Numerics/src/FixedPoint/LookupTable.qs +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -112,7 +112,7 @@ namespace Microsoft.Quantum.Arithmetic { let lookupOperation = LookupOperationWrapper(minInFxP, outBits, numSwapBits, _, _); - return MakeLookupReturn( + return FunctionWithLookupTable( pIn, qIn, pOut, qOut, lookupOperation From f9d2ed48e0c8f1e31c6dec5e43434a32c8ea2a43 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Fri, 19 Aug 2022 15:56:38 +0200 Subject: [PATCH 06/17] Update Numerics/tests/LookupTableTests.qs Co-authored-by: Mathias Soeken --- Numerics/tests/LookupTableTests.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Numerics/tests/LookupTableTests.qs b/Numerics/tests/LookupTableTests.qs index 188a83c36aa..5a5b4221e6c 100755 --- a/Numerics/tests/LookupTableTests.qs +++ b/Numerics/tests/LookupTableTests.qs @@ -19,7 +19,7 @@ namespace Microsoft.Quantum.Tests { let epsIn = 1e-3; let epsOut = 1e-3; - let lookup = MakeLookup(ExpD, (xMin, xMax), epsIn, epsOut, numSwapBits); + let lookup = ApplyFunctionWithLookupTable(ExpD, (xMin, xMax), epsIn, epsOut, numSwapBits); // Check that input and output registers are the expected size EqualityFactI(lookup::IntegerBitsIn + lookup::FractionalBitsIn, 15, "Number of input bits is incorrect"); EqualityFactI(lookup::IntegerBitsOut + lookup::FractionalBitsOut, 25, "Number of output bits is incorrect"); From af74c18a97c54e78d7093a11b0d128b1363c1302 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Fri, 19 Aug 2022 15:56:45 +0200 Subject: [PATCH 07/17] Update Numerics/tests/LookupTableTests.qs Co-authored-by: Mathias Soeken --- Numerics/tests/LookupTableTests.qs | 1 - 1 file changed, 1 deletion(-) diff --git a/Numerics/tests/LookupTableTests.qs b/Numerics/tests/LookupTableTests.qs index 5a5b4221e6c..c697e5e62c7 100755 --- a/Numerics/tests/LookupTableTests.qs +++ b/Numerics/tests/LookupTableTests.qs @@ -60,7 +60,6 @@ namespace Microsoft.Quantum.Tests { EqualityWithinToleranceFact(inResult, inputValue, 1e-3); EqualityWithinToleranceFact(outResult, expected, 1e-3); - Message($"{inResult} {outResult}"); } } From 0460650766c13480544a7982c2ef53d10ac3ef40 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Fri, 19 Aug 2022 15:57:36 +0200 Subject: [PATCH 08/17] Update Numerics/tests/LookupTableTests.qs Co-authored-by: Mathias Soeken --- Numerics/tests/LookupTableTests.qs | 1 - 1 file changed, 1 deletion(-) diff --git a/Numerics/tests/LookupTableTests.qs b/Numerics/tests/LookupTableTests.qs index c697e5e62c7..5b4e7fd7539 100755 --- a/Numerics/tests/LookupTableTests.qs +++ b/Numerics/tests/LookupTableTests.qs @@ -91,7 +91,6 @@ namespace Microsoft.Quantum.Tests { EqualityWithinToleranceFact(inputResult, x, epsIn); EqualityWithinToleranceFact(outputResult, func(inputResult), epsOut); - Message($"Input value: {inputResult}, Output value:{outputResult}"); } } } \ No newline at end of file From de645752957ac5b34490ff70c5d21cf9238cd8e2 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Fri, 19 Aug 2022 15:58:01 +0200 Subject: [PATCH 09/17] Update Numerics/src/FixedPoint/LookupTable.qs Co-authored-by: Mathias Soeken --- Numerics/src/FixedPoint/LookupTable.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs index 60dae13f5b9..792d1b92a0a 100755 --- a/Numerics/src/FixedPoint/LookupTable.qs +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -43,7 +43,7 @@ namespace Microsoft.Quantum.Arithmetic { /// The maximum allowed error of the output without taking into account the error in input value (i.e. |f'(x')-f(x')|) /// ## numSwapBits /// The number of bits of the input register that will be used in the SWAP section of the circuits. Another way of looking - /// at this is that in step in the SELECT section of the circuit in Fig 1c of arXiv:1812.00954, we will encode 2^numSawpBits + /// at this is that in step in the SELECT section of the circuit in Fig 1c of arXiv:1812.00954, we will encode 2^numSwapBits /// encoded function ApplyFunctionWithLookupTable(func: Double -> Double, domain: (Double, Double), epsIn: Double, epsOut: Double, numSwapBits: Int): FunctionWithLookupTable { From edeaf09e8b82149d15867e5652c56f7cadd362f8 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Fri, 19 Aug 2022 15:58:37 +0200 Subject: [PATCH 10/17] Update Numerics/src/FixedPoint/LookupTable.qs Co-authored-by: Mathias Soeken --- Numerics/src/FixedPoint/LookupTable.qs | 1 - 1 file changed, 1 deletion(-) diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs index 792d1b92a0a..49e757b0c01 100755 --- a/Numerics/src/FixedPoint/LookupTable.qs +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -196,7 +196,6 @@ namespace Microsoft.Quantum.Arithmetic { let unitaries = Mapped(MakeWriteBitsUnitary, data); MultiplexOperations(unitaries, LittleEndian(addressRegister), outputRegister); - // MultiplexOperationsWithUnaryIteration(unitaries, LittleEndian(addressRegister), outputRegister); } /// # Summary From 2a9b63a10a5815c5d74f0dff199da4631582ae12 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Fri, 19 Aug 2022 15:58:50 +0200 Subject: [PATCH 11/17] Update Numerics/src/FixedPoint/LookupTable.qs Co-authored-by: Mathias Soeken --- Numerics/src/FixedPoint/LookupTable.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs index 49e757b0c01..2b7ca0454d3 100755 --- a/Numerics/src/FixedPoint/LookupTable.qs +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -225,7 +225,7 @@ namespace Microsoft.Quantum.Arithmetic { } /// # Summary - /// Impelements an efficient control swap (with 2 CNOTs and one CCNOT) + /// Implements an efficient control swap (with 2 CNOTs and one CCNOT) internal operation ApplyLowDepthCSWAP(control : Qubit, target1 : Qubit, target2 : Qubit) : Unit is Adj { use helper = Qubit(); From 81a6827e96167c3dc7a9d80a779d83a2a6aae856 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Fri, 19 Aug 2022 17:57:00 +0200 Subject: [PATCH 12/17] Update Numerics/tests/LookupTableTests.qs Co-authored-by: Mathias Soeken --- Numerics/tests/LookupTableTests.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Numerics/tests/LookupTableTests.qs b/Numerics/tests/LookupTableTests.qs index 5b4e7fd7539..a01a37582f3 100755 --- a/Numerics/tests/LookupTableTests.qs +++ b/Numerics/tests/LookupTableTests.qs @@ -93,4 +93,4 @@ namespace Microsoft.Quantum.Tests { } } -} \ No newline at end of file +} From 65069c9ab5acd6e042f702dd749ca866c0e2c934 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Sun, 21 Aug 2022 12:21:29 +0200 Subject: [PATCH 13/17] Moved potential efficiency comments Moved potential efficiency comments of fanning out control qubits from summary to inside the code --- Numerics/src/FixedPoint/LookupTable.qs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs index 2b7ca0454d3..1512c1f3e2e 100755 --- a/Numerics/src/FixedPoint/LookupTable.qs +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -139,10 +139,6 @@ namespace Microsoft.Quantum.Arithmetic { /// Qubit FixedPoint register containing input values /// ## output /// Qubit FixedPoint register containing where output values will be stored - /// - /// # Potential improvements TODO - /// fanout inner and outer controls - /// make version with garbage internal operation LookupOperationWrapper(minInFxP: Double, outBits: Bool[][], numSwapBits : Int, input: FixedPoint, output: FixedPoint) : Unit is Adj { let integerBitsIn = input::IntegerBits; @@ -154,7 +150,7 @@ namespace Microsoft.Quantum.Arithmetic { // it, because the lookup table always assumes that the miminum value is 00...0 and the maximum value is 11...1 in incrementing order, // so we are re-defining the minimum number as represented by 00...0 and hence subracting the minimum from our value. // (We add the minimum back after making the lookup table) - within { + within { // Currently we always uncompute the lookup table garbage qubits, but we can think of making an option to remove the uncomputation (and keep the garbage qubits) if minInFxP != 0.0 { // Make a new fixed point register to store the minimum vlaue use minRegister = Qubit[Length(registerIn)]; @@ -304,7 +300,7 @@ namespace Microsoft.Quantum.Arithmetic { // Now we split up the register in to chunks of m let chunkedDataRegister = Chunks(m, dataRegister); //Chunks(nElements, array): splits array into chunks, where each chunk has nElements, e.g. Chunks(2, [1, 2, 3, 4, 5, 6]) -> [[1, 2], [3, 4], [5, 6]] - // Perform select swap with the (1) dataArray for the select outputs and (2) chunkedDataRegister for the swap targets + // Perform select swap with the (1) dataArray for the select outputs and (2) chunkedDataRegister for the swap targets. We can think about improving the efficiency of these two steps by thinking of the fanout inner and outer controls more carefully. within { Select(dataArray, addressRegisterParts[0], dataRegister); // apply select using first part of address registers SwapDataOutputs(addressRegisterParts[1], chunkedDataRegister); // apply swap using second part of address registers From 8daa54dc4eed56ab132e6a54f98a1f1626f2f217 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Sun, 21 Aug 2022 12:24:15 +0200 Subject: [PATCH 14/17] Split summary into summary and remarks Split original summary to summary and remarks --- Numerics/src/FixedPoint/LookupTable.qs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs index 1512c1f3e2e..db3e2fcb433 100755 --- a/Numerics/src/FixedPoint/LookupTable.qs +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -10,7 +10,10 @@ namespace Microsoft.Quantum.Arithmetic { /// The return type when making a lookup table. This contains the operation that /// makes the lookup table circuit, as well as all the parameters required to make /// the two FixedPoint registers that need to be used as inputs and outputs to the - /// operator. The reason we have this structure is so that the operator is similar + /// operator. + /// + /// # Remarks + /// The reason we have this return type structure is so that the operator is similar /// to the other typical Q# arithmetic function implementations (a larger discussion /// can had as to whether that can be changed) newtype FunctionWithLookupTable = ( From 6396abaa60ecccac69ce20af28ee2087f5e32e92 Mon Sep 17 00:00:00 2001 From: Rajiv Krishnakumar Date: Sun, 21 Aug 2022 12:27:18 +0200 Subject: [PATCH 15/17] Added latex formatting --- Numerics/src/FixedPoint/LookupTable.qs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs index db3e2fcb433..6871b278387 100755 --- a/Numerics/src/FixedPoint/LookupTable.qs +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -29,10 +29,12 @@ namespace Microsoft.Quantum.Arithmetic { /// the parameters required to make the two FixedPoint registers that need to be used as inputs to the operator. /// This is so that it is in similar to the other typical Q# arithmetic function (a larger discussion can be had /// as to whether that can be changed). The circuit for the operator can be found in Fig. 1c in arXiv:1812.00954. - /// The operator guarantees that given an input value x and a function f(x), - /// it will compute f'(x') where f' is an approximation of f with a maximum error of epsOut and x' is an - /// approximation of the input value x with a maximum error of epsIn. This is useful for most reasonably behaved - /// functions, but not that it computes f'(x') and not f'(x) so if the domain function is very oscillatory and/or + /// + /// # Remarks + /// The operator guarantees that given an input value $x$ and a function $f(x)$, + /// it will compute $\hat{f}(\hat{x})$ where $\hat{f}$ is an approximation of $f$ with a maximum error of epsOut and $\hat{x}$ is an + /// approximation of the input value $\hat{x}$ with a maximum error of epsIn. This is useful for most reasonably behaved + /// functions, but note that it computes $\hat{f}(\hat{x})$ and not $\hat{f}(x)$ so if the domain function is very oscillatory and/or /// has funky derivatives then it may have high errors. /// /// # Input From 3dd9ad991b1bf0377d353be81d59e591391f7e94 Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Mon, 22 Aug 2022 04:53:22 -0700 Subject: [PATCH 16/17] Update Numerics/src/FixedPoint/LookupTable.qs --- Numerics/src/FixedPoint/LookupTable.qs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs index 6871b278387..9a4024d1c3d 100755 --- a/Numerics/src/FixedPoint/LookupTable.qs +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -50,6 +50,29 @@ namespace Microsoft.Quantum.Arithmetic { /// The number of bits of the input register that will be used in the SWAP section of the circuits. Another way of looking /// at this is that in step in the SELECT section of the circuit in Fig 1c of arXiv:1812.00954, we will encode 2^numSwapBits /// encoded + /// + /// # Example + /// The following code creates a quantum operation based on `ExpD` in the (inclusive) range from `-5.0` to `5.0` with an input error of `1e-3` and an output error of `1e-4`. It uses `2` SWAP bits for the implementation. + /// + /// ```qsharp + /// // Create operation from lookup table + /// let domain = (-5.0, 5.0); + /// let epsIn = 1e-3; + /// let epsOut = 1e-4; + /// + /// let lookup = ApplyFunctionWithLookupTable(ExpD, domain, epsIn, epsOut, 2); + /// + /// // Allocate qubits + /// use input = Qubit[lookup::IntegerBitsIn + lookup::FractionalBitsIn]; + /// use output = Qubit[lookup::IntegerBitsOut + lookup::FractionalBitsOut]; + /// + /// // Represent qubit registers as fixed points + /// let inputFxP = FixedPoint(lookup::IntegerBitsIn, input); + /// let outputFxP = FixedPoint(lookup::IntegerBitsOut, output); + /// + /// // Apply operation + /// lookup::Apply(inputFxP, outputFxP); + /// ``` function ApplyFunctionWithLookupTable(func: Double -> Double, domain: (Double, Double), epsIn: Double, epsOut: Double, numSwapBits: Int): FunctionWithLookupTable { // First step is to find the number of integer bits (pIn) and fractional bits (qIn) required for the input based on the From 0018ec7e77c33b79b278c1bd80d40e145dd01fdc Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Mon, 22 Aug 2022 05:18:28 -0700 Subject: [PATCH 17/17] Update Numerics/tests/LookupTableTests.qs --- Numerics/tests/LookupTableTests.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Numerics/tests/LookupTableTests.qs b/Numerics/tests/LookupTableTests.qs index a01a37582f3..fdfb535713a 100755 --- a/Numerics/tests/LookupTableTests.qs +++ b/Numerics/tests/LookupTableTests.qs @@ -71,7 +71,7 @@ namespace Microsoft.Quantum.Tests { let epsIn = 0.125; let epsOut = 0.25; - let lookup = FunctionWithLookupTable(func, (xMin, xMax), epsIn, epsOut, 0); + let lookup = ApplyFunctionWithLookupTable(func, (xMin, xMax), epsIn, epsOut, 0); use inputRegister = Qubit[lookup::IntegerBitsIn+lookup::FractionalBitsIn]; use outputRegister = Qubit[lookup::IntegerBitsOut+lookup::FractionalBitsOut];