Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.

Commit 7812ab9

Browse files
Revise ApplyFunctionWithLookupTable (#617) (#619)
Co-authored-by: Mathias Soeken <[email protected]>
1 parent 2991f67 commit 7812ab9

File tree

2 files changed

+17
-176
lines changed

2 files changed

+17
-176
lines changed

Numerics/src/FixedPoint/LookupTable.qs

Lines changed: 13 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,13 @@ namespace Microsoft.Quantum.Arithmetic {
2525
);
2626

2727
/// # Summary
28-
/// This function creates a select-swap lookup table operator for the function that you want to approximate, as well as
29-
/// the parameters required to make the two FixedPoint registers that need to be used as inputs to the operator.
30-
/// This is so that it is in similar to the other typical Q# arithmetic function (a larger discussion can be had
31-
/// as to whether that can be changed). The circuit for the operator can be found in Fig. 1c in arXiv:1812.00954.
28+
/// This function creates a lookup table operator for the function that you want to approximate, as well as
29+
/// the parameters required to make the two `FixedPoint` registers that need to be used as inputs to the operator.
3230
///
3331
/// # Remarks
3432
/// The operator guarantees that given an input value $x$ and a function $f(x)$,
3533
/// 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
36-
/// approximation of the input value $\hat{x}$ with a maximum error of epsIn. This is useful for most reasonably behaved
34+
/// approximation of the input value $\hat{x}$ with a maximum error of `epsIn`. This is useful for most reasonably behaved
3735
/// 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
3836
/// has funky derivatives then it may have high errors.
3937
///
@@ -46,21 +44,17 @@ namespace Microsoft.Quantum.Arithmetic {
4644
/// The maximum allowed error of the input value to the computation (i.e. |x'-x|)
4745
/// ## epsOut
4846
/// The maximum allowed error of the output without taking into account the error in input value (i.e. |f'(x')-f(x')|)
49-
/// ## numSwapBits
50-
/// The number of bits of the input register that will be used in the SWAP section of the circuits. Another way of looking
51-
/// 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
52-
/// encoded
5347
///
5448
/// # Example
55-
/// 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.
49+
/// 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`.
5650
///
5751
/// ```qsharp
5852
/// // Create operation from lookup table
5953
/// let domain = (-5.0, 5.0);
6054
/// let epsIn = 1e-3;
6155
/// let epsOut = 1e-4;
6256
///
63-
/// let lookup = ApplyFunctionWithLookupTable(ExpD, domain, epsIn, epsOut, 2);
57+
/// let lookup = ApplyFunctionWithLookupTable(ExpD, domain, epsIn, epsOut);
6458
///
6559
/// // Allocate qubits
6660
/// use input = Qubit[lookup::IntegerBitsIn + lookup::FractionalBitsIn];
@@ -73,7 +67,7 @@ namespace Microsoft.Quantum.Arithmetic {
7367
/// // Apply operation
7468
/// lookup::Apply(inputFxP, outputFxP);
7569
/// ```
76-
function ApplyFunctionWithLookupTable(func: Double -> Double, domain: (Double, Double), epsIn: Double, epsOut: Double, numSwapBits: Int): FunctionWithLookupTable {
70+
function ApplyFunctionWithLookupTable(func: Double -> Double, domain: (Double, Double), epsIn: Double, epsOut: Double): FunctionWithLookupTable {
7771

7872
// First step is to find the number of integer bits (pIn) and fractional bits (qIn) required for the input based on the
7973
// 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 {
137131
// Now we use the fixed point approximation of the minimum value of the input
138132
// and the list of output bit values to make the operation lookupOperation: (FixedPoint, FixedPoint) => Unit
139133
// More comments on how that's done in within the function
140-
let lookupOperation = LookupOperationWrapper(minInFxP, outBits, numSwapBits, _, _);
134+
let lookupOperation = LookupOperationWrapper(minInFxP, outBits, _, _);
141135

142136

143137
return FunctionWithLookupTable(
@@ -159,15 +153,11 @@ namespace Microsoft.Quantum.Arithmetic {
159153
/// ## outBits
160154
/// The list of output values in bits in order, where the first value is the output for the smallest input value and
161155
/// the last value is the output for the largest input value
162-
/// ## numSwapBits
163-
/// The number of bits of the input register that will be used in the SWAP section of the circuits. Another way of looking
164-
/// 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
165-
/// encoded
166156
/// ## input
167157
/// Qubit FixedPoint register containing input values
168158
/// ## output
169159
/// Qubit FixedPoint register containing where output values will be stored
170-
internal operation LookupOperationWrapper(minInFxP: Double, outBits: Bool[][], numSwapBits : Int, input: FixedPoint, output: FixedPoint) : Unit is Adj {
160+
internal operation LookupOperationWrapper(minInFxP: Double, outBits: Bool[][], input: FixedPoint, output: FixedPoint) : Unit is Adj {
171161

172162
let integerBitsIn = input::IntegerBits;
173163
let registerIn = input::Register;
@@ -190,7 +180,11 @@ namespace Microsoft.Quantum.Arithmetic {
190180
}
191181
}
192182
} apply {
193-
SelectSwap(numSwapBits, outBits, input::Register, output::Register);
183+
let n = Length(input::Register);
184+
let nRequired = Ceiling(Lg(IntAsDouble(Length(outBits))));
185+
Fact(nRequired <= n, "Too few address bits");
186+
let addressRegisterFitted = input::Register[...nRequired - 1];
187+
Select(outBits, input::Register[...nRequired - 1], output::Register);
194188
}
195189
}
196190

@@ -221,145 +215,4 @@ namespace Microsoft.Quantum.Arithmetic {
221215
let unitaries = Mapped(MakeWriteBitsUnitary, data);
222216
MultiplexOperations(unitaries, LittleEndian(addressRegister), outputRegister);
223217
}
224-
225-
/// # Summary
226-
/// This operation makes the swap circuit. The outputRegisters are 2^l qubit registers, each of size m
227-
internal operation SwapDataOutputs(addressRegister: Qubit[], outputRegisters: Qubit[][]) : Unit is Adj {
228-
let l = Length(addressRegister);
229-
// For each input qubit we are using for swap qubits, we want to implement all the swaps
230-
for i in 0.. Length(addressRegister)-1{
231-
// 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,
232-
// and we need to swap atotal of 2^l/2^(i+1) registers, where l is the number of output registers. E.g.
233-
// i=0 => swaps between registers (0,1), (2,3), (4,5),..., (2^l - 2, 2^l - 1)
234-
// i=1 => swaps between registers (0,2), (4,6), (8,10),..., (2^l - 4, 2^l - 2)
235-
// 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)
236-
let innerStepSize = 2^i;
237-
let outerStepSize = 2^(i+1);
238-
let numSwaps = 2^l/2^(i+1);
239-
use extraControls = Qubit[numSwaps-1];
240-
let fannedControls = [addressRegister[i]] + extraControls;
241-
within {
242-
ApplyToEachA(CNOT(addressRegister[i],_), extraControls); // Fanning out qubits to be able to do a fanned control
243-
} apply {
244-
for j in 0.. numSwaps-1{
245-
ApplyMultiTargetSwap(fannedControls[j], outputRegisters[j*outerStepSize], outputRegisters[j*outerStepSize+innerStepSize]);
246-
}
247-
}
248-
}
249-
}
250-
251-
/// # Summary
252-
/// Implements an efficient control swap (with 2 CNOTs and one CCNOT)
253-
internal operation ApplyLowDepthCSWAP(control : Qubit, target1 : Qubit, target2 : Qubit) : Unit is Adj {
254-
use helper = Qubit();
255-
256-
within {
257-
CNOT(target2, target1);
258-
ApplyLowDepthAnd(control, target1, helper); // this has T-depth 1, AND = CCNOT where target is in |0>
259-
} apply {
260-
CNOT(helper, target2);
261-
}
262-
}
263-
264-
/// # Summary
265-
/// Implements a control swap two registers controlled on a single qubits. To be able to parallelize it, it will
266-
/// fan out the control register and perform all the swaps in parallel
267-
internal operation ApplyMultiTargetSwap(control : Qubit, target1 : Qubit[], target2 : Qubit[]) : Unit is Adj {
268-
EqualityFactI(Length(target1), Length(target2), "The two qubit registers are of different sizes");
269-
270-
use extraControls = Qubit[Length(target1) - 1];
271-
let fannedControls = [control] + extraControls;
272-
273-
within {
274-
ApplyToEachA(CNOT(control, _), extraControls);
275-
} apply {
276-
for i in 0..Length(target1)-1{
277-
ApplyLowDepthCSWAP(fannedControls[i], target1[i], target2[i]);
278-
}
279-
}
280-
}
281-
282-
/// # Summary
283-
/// Creates the select-swap circuit. Uses the most significant bits of `addressRegister` to use for SWAP network.
284-
/// Let n be `Length(addressRegister)`, the number of address bits, and let l be `numSwapBits`. Then the T-depth is
285-
/// approximately O(2^{(n-l}) + l. If we want m output qubits, the number of additional ancilla qubits is m * 2^l:
286-
/// These due additional ancilla qubits come from the swap
287-
///
288-
/// # Input
289-
/// ## numSwapBits
290-
/// The number of bits of the input register that will be used in the SWAP section of the circuits. Another way of looking
291-
/// 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
292-
/// encoded.
293-
/// ## data
294-
/// The list of output values in bits in order, where the first value is the output for the value 00...0 and
295-
/// the last value is the output for the value the largest integer value
296-
/// ## addressRegister
297-
/// Input register of qubits
298-
/// ## outputRegister
299-
/// Output register where the output values will be stored
300-
internal operation SelectSwap(numSwapBits : Int, data : Bool[][], addressRegister: Qubit[], outputRegister: Qubit[]) : Unit is Adj{
301-
302-
let n = Length(addressRegister);
303-
304-
// 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
305-
let nRequired = Ceiling(Lg(IntAsDouble(Length(data))));
306-
Fact(nRequired <= n, "Too few address bits");
307-
let addressRegisterFitted = addressRegister[...nRequired - 1];
308-
309-
// Probably numSwapBits == nRequired works, but has to be checked
310-
Fact(numSwapBits < nRequired, "Too many bits for SWAP network");
311-
312-
if numSwapBits == 0 { // to not uncompute select if l=0
313-
Select(data, addressRegisterFitted, outputRegister);
314-
} else {
315-
let m = Length(outputRegister);
316-
// number of SWAP bits
317-
let l = numSwapBits;
318-
// number of SELECT bits
319-
let k = nRequired - numSwapBits;
320-
321-
let addressRegisterParts = Partitioned([k, l], addressRegisterFitted); //creates two parts of the array - 1 for the select and 1 for the swaps
322-
323-
use dataRegister = Qubit[m * 2^l]; //create one register with m*2^l qubits to have all the outputs stored
324-
325-
// 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.
326-
let dataArray = CreatePaddedData(data, nRequired, m, k);
327-
328-
// Now we split up the register in to chunks of m
329-
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]]
330-
331-
// 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.
332-
within {
333-
Select(dataArray, addressRegisterParts[0], dataRegister); // apply select using first part of address registers
334-
SwapDataOutputs(addressRegisterParts[1], chunkedDataRegister); // apply swap using second part of address registers
335-
} apply {
336-
ApplyToEachA(CNOT, Zipped(chunkedDataRegister[0], outputRegister)); // apply CNOT from chunkedDataRegister[0] to outputRegister
337-
}
338-
}
339-
}
340-
341-
/// # Summary
342-
/// Helper function that creates a list of binary numbers were each number is a concatination of all the possible outputs of each
343-
/// select function (i.e. combining all the possible outputs into a single bitstring which will then be used in the swap bit of the
344-
/// select-swap)
345-
///
346-
/// # Input
347-
/// ## data
348-
/// The list of output values in bits in order, where the first value is the output for the value 00...0 and
349-
/// the last value is the output for the value the largest integer value
350-
/// ## nRequired
351-
/// Minimum number of address bits required to ensure the largest data point can be stored
352-
/// ## m
353-
/// Size of final single output of lookup table
354-
/// ## k
355-
/// Number of bits from input that will be used in the select part
356-
internal function CreatePaddedData(data : Bool[][], nRequired : Int, m : Int, k : Int) : Bool[][] {
357-
let dataPadded = Padded(-2^nRequired, [false, size = m], data); // Padded so that we don't have problems for powers of 2
358-
mutable dataArray = [[], size = 2^k];
359-
for i in 0..2^k-1 {
360-
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...]
361-
set dataArray w/= i <- Flattened(Subarray(range, dataPadded)); // Subarray(indices, array) takes indices Int[] and returns subarray of array subject to indices
362-
}
363-
return dataArray;
364-
}
365218
}

Numerics/tests/LookupTableTests.qs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,19 @@ namespace Microsoft.Quantum.Tests {
1212
/// Example of exponential operation with [0, 9.23] with input-eps 1e-3, output-eps 1e-3
1313
/// Input should require something like 5 integer bits and 10 fractional bits
1414
/// Output should require something like 15 integer bits and 10 fractional bits
15-
operation ExponentialExample(numSwapBits: Int, input : FixedPoint, output : FixedPoint) : Unit {
15+
operation ExponentialExample(input : FixedPoint, output : FixedPoint) : Unit {
1616
let func = ExpD;
1717
let xMin = 0.0;
1818
let xMax = 9.23;
1919
let epsIn = 1e-3;
2020
let epsOut = 1e-3;
2121

22-
let lookup = ApplyFunctionWithLookupTable(ExpD, (xMin, xMax), epsIn, epsOut, numSwapBits);
22+
let lookup = ApplyFunctionWithLookupTable(ExpD, (xMin, xMax), epsIn, epsOut);
2323
// Check that input and output registers are the expected size
2424
EqualityFactI(lookup::IntegerBitsIn + lookup::FractionalBitsIn, 15, "Number of input bits is incorrect");
2525
EqualityFactI(lookup::IntegerBitsOut + lookup::FractionalBitsOut, 25, "Number of output bits is incorrect");
2626
lookup::Apply(input, output);
2727
}
28-
29-
/// # Summary
30-
/// Example to call Exponential with pre-computed number of input and
31-
/// output qubits from ExponentialExample
32-
operation EstimateExponentialInstance(numSwapBits : Int) : Unit {
33-
use input = Qubit[15];
34-
use output = Qubit[25];
35-
let inputFxP = FixedPoint(5, input);
36-
let outputFxP = FixedPoint(15, output);
37-
38-
ExponentialExample(numSwapBits, inputFxP, outputFxP);
39-
}
4028

4129
//Tests for some examples
4230
@Test("ToffoliSimulator")
@@ -50,7 +38,7 @@ namespace Microsoft.Quantum.Tests {
5038
let inputValue = DrawRandomDouble(9.23*IntAsDouble(i)/10.0, 9.23*IntAsDouble(i+1)/10.0);
5139

5240
PrepareFxP(inputValue, inputFxP);
53-
ExponentialExample(5, inputFxP, outputFxP);
41+
ExponentialExample(inputFxP, outputFxP);
5442

5543
let inResult = MeasureFxP(inputFxP);
5644
let outResult = MeasureFxP(outputFxP);
@@ -71,7 +59,7 @@ namespace Microsoft.Quantum.Tests {
7159
let epsIn = 0.125;
7260
let epsOut = 0.25;
7361

74-
let lookup = ApplyFunctionWithLookupTable(func, (xMin, xMax), epsIn, epsOut, 0);
62+
let lookup = ApplyFunctionWithLookupTable(func, (xMin, xMax), epsIn, epsOut);
7563

7664
use inputRegister = Qubit[lookup::IntegerBitsIn+lookup::FractionalBitsIn];
7765
use outputRegister = Qubit[lookup::IntegerBitsOut+lookup::FractionalBitsOut];

0 commit comments

Comments
 (0)