forked from microsoft/Quantum
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSimpleAlgorithms.qs
490 lines (384 loc) Β· 21.8 KB
/
SimpleAlgorithms.qs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// First, note that every Q# function must have a namespace. We define
// a new one for this purpose.
namespace Microsoft.Quantum.Samples.SimpleAlgorithms {
open Microsoft.Quantum.Primitive;
open Microsoft.Quantum.Canon;
//////////////////////////////////////////////////////////////////////////
// Introduction //////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// This sample contains several simple quantum algorithms coded in Q#. The
// intent is to highlight the expressive capabilities of the language that
// enable it to express quantum algorithms that consist of a short quantum
// part and classical post-processing that is simple, or in some cases,
// trivial.
//////////////////////////////////////////////////////////////////////////
// BernsteinβVazirani Fourier Sampling Quantum Algorithm //////////////////
//////////////////////////////////////////////////////////////////////////
/// # Summary
/// ParityViaFourierSampling implements the Bernstein-Vazirani quantum algorithm.
/// This algorithm computes for a given Boolean function that is promised to be
/// a parity π(π₯β, β¦, π₯βββ) = Ξ£α΅’ πα΅’ π₯α΅’ a result in form of
/// a bit vector (πβ, β¦, πβββ) corresponding to the parity function.
/// Note that it is promised that the function is actually a parity function.
///
/// # Input
/// ## Uf
/// A quantum operation that implements |π₯βͺ|π¦βͺ β¦ |π₯βͺ|π¦ β π(π₯)βͺ,
/// where π is a Boolean function that implements a parity Ξ£α΅’ πα΅’ π₯α΅’.
/// ## n
/// The number of bits of the input register |π₯βͺ.
///
/// # Output
/// An array of type `Bool[]` that contains the parity πβ = (πβ, β¦, πβββ).
///
/// # See Also
/// - For details see Section 1.4.3 of Nielsen & Chuang.
///
/// # References
/// - [ *Ethan Bernstein and Umesh Vazirani*,
/// SIAM J. Comput., 26(5), 1411β1473, 1997 ](https://doi.org/10.1137/S0097539796300921)
operation ParityViaFourierSampling (Uf : (Qubit[] => Unit), n : Int) : Bool[] {
// We first create an array of size n which will hold the final result.
mutable resultArray = new Result[n];
// Now, we allocate n + 1 clean qubits. Note that the function Uf is defined
// on inputs of the form (x, y), where x has n bits and y has 1 bit.
using (qubits = Qubit[n + 1]) {
// The last qubit needs to be flipped so that the function will
// actually be computed into the phase when Uf is applied.
X(qubits[n]);
// Now, a Hadamard transform is applied to each of the qubits.
ApplyToEach(H, qubits);
// We now apply Uf to the n+1 qubits, computing |x, yβͺ β¦ |x, y β f(x)βͺ.
Uf(qubits);
// As the last step before the measurement, a Hadamard transform is
// applied to all qubits except last one. We could apply the transform to
// the last qubit also, but this would not affect the final outcome.
ApplyToEach(H, qubits[0 .. n - 1]);
// The following for-loop measures all qubits and resets them to
// zero so that they can be safely returned at the end of the
// using-block.
for (idx in 0 .. n - 1) {
set resultArray[idx] = MResetZ(qubits[idx]);
}
// Finally, the last qubit, which held the y-register, is reset.
Reset(qubits[n]);
}
// The result is already contained in resultArray so no further
// post-processing is necessary.
Message($"measured: {resultArray}");
return BoolArrFromResultArr(resultArray);
}
// To demonstrate the BernsteinβVazirani algorithm, we define
// a function which returns black-box operations (Qubit[] => ()) of
// the form
// U_f |π₯βͺ|π¦βͺ = |π₯βͺ|π¦ β π(π₯)βͺ,
// as described above.
// In particular, we define π by providing the pattern πβ. Thus, we can
// easily assert that the pattern measured by the BernsteinβVazirani
// algorithm matches the pattern we used to define π.
// As is idiomatic in Q#, we define an operation that we will typically
// only call by partially applying it from within a matching function.
// To indicate that we are using this idiom, we name the operation
// with the suffix "Impl", and provide documentation comments for the
// function itself.
operation ParityOperationImpl (pattern : Bool[], qs : Qubit[]) : Unit {
let n = Length(pattern);
if (Length(qs) != n + 1) {
fail "Length of qs must be equal to pattern length + 1.";
}
for (idx in 0 .. n - 1) {
if (pattern[idx]) {
Controlled X([qs[idx]], qs[n]);
}
}
}
/// # Summary
/// Given a bitstring πβ = (rβ, β¦, rβββ), returns an operation implementing
/// a unitary π that acts on π + 1 qubits as
///
/// π |π₯βͺ|π¦βͺ = |π₯βͺ|π¦ β π(π₯)βͺ,
/// where π(π₯) = Ξ£α΅’ π₯α΅’ πα΅’ mod 2.
///
/// # Input
/// ## pattern
/// The bitstring πβ used to define the function π.
///
/// # Output
/// An operation implementing π.
function ParityOperation (pattern : Bool[]) : (Qubit[] => Unit) {
return ParityOperationImpl(pattern, _);
}
// For convenience, we provide an additional operation with a signature
// that's easy to call from C#. In particular, we define our new operation
// to take an Int as input and to return an Int as output, where each
// Int represents a bitstring using the little endian convention.
operation BernsteinVaziraniTestCase (nQubits : Int, patternInt : Int) : Int {
let pattern = BoolArrFromPositiveInt(patternInt, nQubits);
let result = ParityViaFourierSampling(ParityOperation(pattern), nQubits);
return PositiveIntFromBoolArr(result);
}
//////////////////////////////////////////////////////////////////////////
// DeutschβJozsa Quantum Algorithm ///////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/// # Summary
/// DeutschβJozsa is a quantum algorithm that decides whether a given Boolean function
/// π that is promised to either be constant or to be balanced β i.e., taking the
/// values 0 and 1 the exact same number of times β is actually constant or balanced.
/// The operation `IsConstantBooleanFunction` answers this question by returning the
/// Boolean value `true` if the function is constant and `false` if it is not. Note
/// that the promise that the function is either constant or balanced is assumed.
///
/// # Input
/// ## Uf
/// A quantum operation that implements |π₯βͺ|π¦βͺ β¦ |π₯βͺ|π¦ β π(π₯)βͺ,
/// where π is a Boolean function, π₯ is an π bit register and π¦ is a single qubit.
/// ## n
/// The number of bits of the input register |π₯βͺ.
///
/// # Output
/// A boolean value `true` that indicates that the function is constant and `false`
/// that indicates that the function is balanced.
///
/// # See Also
/// - For details see Section 1.4.3 of Nielsen & Chuang.
///
/// # References
/// - [ *Michael A. Nielsen , Isaac L. Chuang*,
/// Quantum Computation and Quantum Information ](http://doi.org/10.1017/CBO9780511976667)
operation IsConstantBooleanFunction (Uf : (Qubit[] => Unit), n : Int) : Bool {
// We first create an array of size n from which we compute the final result.
mutable resultArray = new Result[n];
// Now, we allocate n + 1 clean qubits. Note that the function Uf is defined
// on inputs of the form (x, y), where x has n bits and y has 1 bit.
using (qubits = Qubit[n + 1]) {
// The last qubit needs to be flipped so that the function will
// actually be computed into the phase when Uf is applied.
X(qubits[n]);
// Now, a Hadamard transform is applied to each of the qubits.
ApplyToEach(H, qubits);
// We now apply Uf to the n + 1 qubits, computing |π₯, π¦βͺ β¦ |π₯, π¦ β π(π₯)βͺ.
Uf(qubits);
// As the last step before the measurement, a Hadamard transform is
// but the very last one. We could apply the Hadamard transform to
// the last qubit also, but this would not affect the final outcome.
ApplyToEach(H, qubits[0 .. n - 1]);
// The following for-loop measures all qubits and resets them to
// zero so that they can be safely returned at the end of the
// using-block.
for (idx in 0 .. n - 1) {
set resultArray[idx] = MResetZ(qubits[idx]);
}
// Finally, the last qubit, which held the π¦-register, is reset.
Reset(qubits[n]);
}
// we use the predicte `IsResultZero` from Microsoft.Quantum.Canon
// (Predicates.qs) and compose it with the ForAll function from
// Microsoft.Quantum.Canon (ForAll.qs). This will return
// `true` if the all zero string has been measured, i.e., if the function
// was a constant function and `false` if not, which according to the
// promise on π means that it must have been balanced.
return ForAll(IsResultZero, resultArray);
}
// As before, we define an operation and a function to construct black-box
// operations and a test case to make it easier to test DeutschβJozsa
// algorithm from a C# driver.
operation BooleanFunctionFromMarkedElementsImpl (n : Int, markedElements : Int[], qs : Qubit[]) : Unit {
let target = qs[Length(qs) - 1];
let inputs = qs[0 .. Length(qs) - 2];
// This operation applies the unitary
// π |π§βͺ |πβͺ = |π§ β π₯ββͺ |πβͺ,
// where π₯β = 1 if π is an contained in the array markedElements.
// Operations of this form represent querying "databases" in
// which some subset of items are marked.
// We will revisit this construction later, in the DatabaseSearch
// sample.
let nMarked = Length(markedElements);
for (idxMarked in 0 .. nMarked - 1) {
// Note: As X accepts a Qubit, and ControlledOnInt only
// accepts Qubit[], we use ApplyToEachCA(X, _) which accepts
// Qubit[] even though the target is only 1 Qubit.
(ControlledOnInt(markedElements[idxMarked], ApplyToEachCA(X, _)))(inputs, [target]);
}
}
/// # Summary
/// Constructs an operation representing a query to a boolean function
/// π(π₯β) for a bitstring π₯β, such that π(π₯β) = 1 if and only if the integer
/// π represented by π₯β is an element of a given array.
///
/// # Input
/// ## nQubits
/// The number of qubits to be used in representing the query operation.
/// ## markedElements
/// An array of the elements {πα΅’} for which π should return 1.
///
/// # Output
/// An operation representing the unitary π |π§βͺ |πβͺ = |π§ β π₯ββͺ |πβͺ.
function BooleanFunctionFromMarkedElements (nQubits : Int, markedElements : Int[]) : (Qubit[] => Unit) {
return BooleanFunctionFromMarkedElementsImpl(nQubits, markedElements, _);
}
operation DeutschJozsaTestCase (nQubits : Int, markedElements : Int[]) : Bool {
return IsConstantBooleanFunction(BooleanFunctionFromMarkedElements(nQubits, markedElements), nQubits);
}
//////////////////////////////////////////////////////////////////////////
// Quantum Algorithm for Hidden Shifts of Bent Functions /////////////////
//////////////////////////////////////////////////////////////////////////
// We finally consider a particular family of problems known as hidden
// shift problems, in which one is given two Boolean functions π and π
// with the promise that they satisfy the relation
// π(π₯) = π(π₯ β π ) for all π₯,
// where π is a hidden bitstring that we would like to find.
// Good quantum algorithms exist for several different families of
// pairs of Boolean functions. In particular, here we consider the case
// in which both π and π are bent functions. We say that a Boolean
// function is bent if it is as far from linear as possible. In
// particular, bent functions have flat Fourier spectra, such that each
// Fourier coefficient is equal in absolute value.
// In this case, the Roetteler algorithm (see References, below) uses
// black-box oracles for π^* and π, where π^* is the dual bent function
// to π (defined in more detail below), and computes the hidden shift π
// between π and π.
/// # Summary
/// Correlation-based algorithm to solve the hidden shift problem for bent functions.
/// The problem is to identify an unknown shift π of the arguments of two Boolean functions
/// π and π that are promised to satisfy the relation π(π₯) = π(π₯ β π ) for all π₯.
/// Note that the promise about the functions π and π to be bent functions is assumed,
/// i.e., they both have a flat Fourier (WalshβHadamard) spectra. Input to this algorithm
/// are implementations π_g of the Boolean function π and π_f^*, an implementation of
/// dual bent function of the function π. Both functions are given via phase encoding.
///
/// # Input
/// ## Ufstar
/// A quantum operation that implements $U_f^*:\ket{x}\mapsto (-1)^{f^*(x)} \ket{x}$,
/// where $f^*$ is a Boolean function, $x$ is an $n$ bit register and $y$ is a single qubit.
/// ## Ug
/// A quantum operation that implements $U_g:\ket{x}\mapsto (-1)^{g(x)} \ket{x}$,
/// where $g$ is a Boolean function that is shifted by unknown $s$ from $f$, and $x$ is
/// an $n$ bit register.
/// ## n
/// The number of bits of the input register |π₯βͺ.
///
/// # Output
/// An array of type `Bool[]` which encodes the bit representation of the hidden shift.
///
/// # References
/// - [*Martin Roetteler*,
/// Proc. SODA 2010, ACM, pp. 448-457, 2010](https://doi.org/10.1137/1.9781611973075.37)
operation HiddenShiftBentCorrelation (Ufstar : (Qubit[] => Unit), Ug : (Qubit[] => Unit), n : Int) : Bool[] {
// we first create an array of size n from which we compute the final result.
mutable resultArray = new Result[n];
// now, we allocate n clean qubits. Note that the function Ufstar and Ug are
// unitary operations on n qubits defined via phase encoding.
using (qubits = Qubit[n]) {
// first, a Hadamard transform is applied to each of the qubits.
ApplyToEach(H, qubits);
// we now apply the shifted function Ug to the n qubits, computing
// |xβͺ -> (-1)^{g(x)} |xβͺ.
Ug(qubits);
// now, a Hadamard transform is applied to each of the n qubits.
ApplyToEach(H, qubits);
// we now apply the dual function of the unshifted function, i.e., Ufstar,
// to the n qubits, computing |xβͺ -> (-1)^{fstar(x)} |xβͺ.
Ufstar(qubits);
// now, a Hadamard transform is applied to each of the n qubits.
ApplyToEach(H, qubits);
// the following for-loop measures the n qubits and resets them to
// zero so that they can be safely returned at the end of the
// using-block.
for (idx in 0 .. n - 1) {
set resultArray[idx] = MResetZ(qubits[idx]);
}
}
// the result is already contained in resultArray and not further
// post-processing is necessary except for a conversion from Result[] to
// Bool[] for which we use a canon function (from TypeConversion.qs).
Message($"measured: {resultArray}");
return BoolArrFromResultArr(resultArray);
}
// We demonstrate this algorithm by defining an operation which implements
// an oracle for a bent function constructed from the inner product of
// Boolean functions.
// In particular, the operation `InnerProductBentFunctionImpl` defines the Boolean
// function IP(x_0, ..., x_{n-1}) which is computed into the phase, i.e.,
// a diagonal operator that maps |xβͺ -> (-1)^{IP(x)} |xβͺ, where x stands for
// x = (x_0, ..., x_{n-1}) and all the x_i are binary. The IP function is
// defined as IP(y, z) = y_0 z_0 + y_1 z_1 + ... y_{u-1} z_{u-1} where
// y = (y_0, ..., y_{u-1}) and z = (z_0, ..., z_{u-1}) are two bit
// vectors of length u. Notice that the function IP is a Boolean function
// on n = 2u bits. IP is a special case of a so-called 'bent' function.
// These are functions for which the Walsh-Hadamard transform is perfectly
// flat (in absolute value). Because of this flatness, the Walsh-Hadamard
// spectrum of any bent function defines a +1/-1 function, i.e., gives
// rise to another Boolean function, called the 'dual bent function'.
// What is more, for the case of the IP function it can be shown that IP
// is equal to its own dual bent function, a fact that is exploited in
// the present test case.
// Notice that a diagonal operator implementing IP between 2 variables
// y_0 and z_0 is nothing but the AND function between those variables, i.e.,
// in phase encoding it is computed by a Controlled-Z gate. Extending this
// to an XOR of the AND of more variables, as required in the definition of
// the IP function can then be accomplished by applying several Controlled-Z
// gates between the respective inputs.
operation InnerProductBentFunctionImpl (u : Int, qs : Qubit[]) : Unit {
if (Length(qs) != 2 * u) {
fail "Length of qs must be twice the value of u";
}
let xs = qs[0 .. u - 1];
let ys = qs[u .. 2 * u - 1];
for (idx in 0 .. u - 1) {
Controlled Z([xs[idx]], ys[idx]);
}
}
// Again, using partial application we create a function which for a given bit
// size u constructs the IP Boolean function on 2u qubits, computed into the phase.
function InnerProductBentFunction (u : Int) : (Qubit[] => Unit) {
return InnerProductBentFunctionImpl(u, _);
}
// To instantiate the hidden shift problem we need another function g which is
// related to IP via g(x) = IP(x + s), i.e., we have to shift the argument of
// the IP function by a given shift. Notice that the '+' operation here is the
// Boolean addition, i.e., a bit-wise operation. Notice further, that in
// general a diagonal operation |xβͺ -> (-1)^{f(x)} can be turned into a shifted
// version by applying a bit flip to the |xβͺ register first, then applying the
// diagonal operation, and then undoing the bit flips to the |xβͺ register. We
// use this principle to define shifted versions of the IP operation.
operation ShiftedInnerProductBentFunctionImpl (shift : Bool[], u : Int, qs : Qubit[]) : Unit {
let n = 2 * u;
if (Length(shift) != n || Length(qs) != n) {
fail "Length of shift and qs must be twice the value of u";
}
// the following loop flips the bits in shift
for (idx in 0 .. n - 1) {
if (shift[idx]) {
X(qs[idx]);
}
}
// now we compute the IP function into the phase
(InnerProductBentFunction(u))(qs);
// the following loop flips the bits in shift
for (idx in 0 .. n - 1) {
if (shift[idx]) {
X(qs[idx]);
}
}
}
// Again, using partial application we construct a function that produces the
// operations that are used to instantiate a particular hidden shift problem
// and are then passed to the quantum algorithm `HiddenShiftBentCorrelation`
// which computes the hidden shift.
function ShiftedInnerProductBentFunction (shift : Bool[], u : Int) : (Qubit[] => Unit) {
return ShiftedInnerProductBentFunctionImpl(shift, u, _);
}
// We finish by providing a case that can be easily called from C#.
operation HiddenShiftBentCorrelationTestCase (patternInt : Int, u : Int) : Int {
let nQubits = 2 * u;
// The integer patternInt is converted to a bit pattern
// using a canon function (from Utils.qs)
let pattern = BoolArrFromPositiveInt(patternInt, nQubits);
// We then convert back to an integer, so that the C# driver
// doesn't need to worry with arrays.
let result = PositiveIntFromBoolArr(HiddenShiftBentCorrelation(InnerProductBentFunction(u), ShiftedInnerProductBentFunction(pattern, u), nQubits));
return result;
}
}