diff --git a/Numerics/src/FixedPoint/LookupTable.qs b/Numerics/src/FixedPoint/LookupTable.qs index 9a4024d1c3d..15675bb54e9 100755 --- a/Numerics/src/FixedPoint/LookupTable.qs +++ b/Numerics/src/FixedPoint/LookupTable.qs @@ -25,15 +25,13 @@ namespace Microsoft.Quantum.Arithmetic { ); /// # 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. + /// This function creates a 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. /// /// # 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 + /// 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. /// @@ -46,13 +44,9 @@ namespace Microsoft.Quantum.Arithmetic { /// 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^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. + /// 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`. /// /// ```qsharp /// // Create operation from lookup table @@ -60,7 +54,7 @@ namespace Microsoft.Quantum.Arithmetic { /// let epsIn = 1e-3; /// let epsOut = 1e-4; /// - /// let lookup = ApplyFunctionWithLookupTable(ExpD, domain, epsIn, epsOut, 2); + /// let lookup = ApplyFunctionWithLookupTable(ExpD, domain, epsIn, epsOut); /// /// // Allocate qubits /// use input = Qubit[lookup::IntegerBitsIn + lookup::FractionalBitsIn]; @@ -73,7 +67,7 @@ namespace Microsoft.Quantum.Arithmetic { /// // Apply operation /// lookup::Apply(inputFxP, outputFxP); /// ``` - function ApplyFunctionWithLookupTable(func: Double -> Double, domain: (Double, Double), epsIn: Double, epsOut: Double, numSwapBits: Int): FunctionWithLookupTable { + function ApplyFunctionWithLookupTable(func: Double -> Double, domain: (Double, Double), epsIn: Double, epsOut: Double): 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 @@ -137,7 +131,7 @@ namespace Microsoft.Quantum.Arithmetic { // 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, _, _); + let lookupOperation = LookupOperationWrapper(minInFxP, outBits, _, _); return FunctionWithLookupTable( @@ -159,15 +153,11 @@ namespace Microsoft.Quantum.Arithmetic { /// ## 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 - internal operation LookupOperationWrapper(minInFxP: Double, outBits: Bool[][], numSwapBits : Int, input: FixedPoint, output: FixedPoint) : Unit is Adj { + internal operation LookupOperationWrapper(minInFxP: Double, outBits: Bool[][], input: FixedPoint, output: FixedPoint) : Unit is Adj { let integerBitsIn = input::IntegerBits; let registerIn = input::Register; @@ -190,7 +180,11 @@ namespace Microsoft.Quantum.Arithmetic { } } } apply { - SelectSwap(numSwapBits, outBits, input::Register, output::Register); + let n = Length(input::Register); + let nRequired = Ceiling(Lg(IntAsDouble(Length(outBits)))); + Fact(nRequired <= n, "Too few address bits"); + let addressRegisterFitted = input::Register[...nRequired - 1]; + Select(outBits, input::Register[...nRequired - 1], output::Register); } } @@ -221,145 +215,4 @@ namespace Microsoft.Quantum.Arithmetic { let unitaries = Mapped(MakeWriteBitsUnitary, data); MultiplexOperations(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 - /// 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(); - - 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. 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 - } 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 index fdfb535713a..9c991b9d62a 100755 --- a/Numerics/tests/LookupTableTests.qs +++ b/Numerics/tests/LookupTableTests.qs @@ -12,31 +12,19 @@ namespace Microsoft.Quantum.Tests { /// 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 { + operation ExponentialExample(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 = ApplyFunctionWithLookupTable(ExpD, (xMin, xMax), epsIn, epsOut, numSwapBits); + let lookup = ApplyFunctionWithLookupTable(ExpD, (xMin, xMax), epsIn, epsOut); // 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") @@ -50,7 +38,7 @@ namespace Microsoft.Quantum.Tests { let inputValue = DrawRandomDouble(9.23*IntAsDouble(i)/10.0, 9.23*IntAsDouble(i+1)/10.0); PrepareFxP(inputValue, inputFxP); - ExponentialExample(5, inputFxP, outputFxP); + ExponentialExample(inputFxP, outputFxP); let inResult = MeasureFxP(inputFxP); let outResult = MeasureFxP(outputFxP); @@ -71,7 +59,7 @@ namespace Microsoft.Quantum.Tests { let epsIn = 0.125; let epsOut = 0.25; - let lookup = ApplyFunctionWithLookupTable(func, (xMin, xMax), epsIn, epsOut, 0); + let lookup = ApplyFunctionWithLookupTable(func, (xMin, xMax), epsIn, epsOut); use inputRegister = Qubit[lookup::IntegerBitsIn+lookup::FractionalBitsIn]; use outputRegister = Qubit[lookup::IntegerBitsOut+lookup::FractionalBitsOut];