microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
brlackey/ising-model-sample

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

library/table_lookup/src/Utils.qs

255lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4import Std.Diagnostics.*;
5import Std.Math.*;
6import Std.Arrays.*;
7import Std.Convert.*;
8import Std.Logical.Xor;
9
10import Lookup.*;
11
12struct AddressAndData {
13 // Lower part of the address needed to index into data.
14 fitAddress : Qubit[],
15 // Data padded or trimmed to fit needed address space.
16 fitData : Bool[][],
17}
18
19function PrepareAddressAndData(
20 options : LookupOptions,
21 address : Qubit[],
22 data : Bool[][]
23) : AddressAndData {
24 let address_size = Length(address);
25 let address_space = 1 <<< address_size;
26 let data_length = Length(data);
27
28 if (data_length == address_space) {
29 // Data length match address space, nothing to adjust.
30 return new AddressAndData {
31 fitAddress = address,
32 fitData = data,
33 };
34 }
35
36 if (data_length > address_space) {
37 // Truncate longer data if needed.
38 if options.failOnLongData {
39 fail $"Data length {data_length} exceeds address space {address_space}.";
40 }
41 return new AddressAndData {
42 fitAddress = address,
43 fitData = data[...address_space-1]
44 };
45 }
46
47 // Data is shorter than addressable space. Truncate excessive address if needed.
48
49 if (not options.failOnShortData) {
50 fail $"Data length {data_length} is shorter than address space {address_space}.";
51 }
52
53 if (options.respectExcessiveAddress) {
54 return new AddressAndData {
55 fitAddress = address,
56 fitData = data,
57 };
58 }
59
60 if (data_length <= 1) {
61 // No address qubits are needed for data length 0.
62 // Case data_length == 1 is for compatibility with earlier behavior.
63 return new AddressAndData {
64 fitAddress = [],
65 fitData = data,
66 };
67 }
68
69 let address_size_needed = BitSizeI(data_length - 1);
70 Fact(address_size_needed <= address_size, "Internal error: address_size_needed should be at most address_size.");
71
72 let address_space_needed = 1 <<< address_size_needed;
73 Fact(address_space_needed >= data_length, "Internal error: address_space_needed should be at least data_length.");
74
75 return new AddressAndData {
76 // Trim address qubits to needed size.
77 fitAddress = address[...address_size_needed - 1],
78 // Shorter data in this case will be handled later.
79 fitData = data,
80 };
81}
82
83/// # Summary
84/// Computes the Fast Möbius Transform of a boolean array over GF(2).
85/// Also known as the Walsh-Hadamard Transform or subset sum transform.
86///
87/// # Description
88/// This transform converts minterm coefficients to monomial coefficients.
89/// For each position i in the result, it computes the XOR (sum over GF(2)) of all
90/// input elements at positions that are subsets of i (when i is interpreted as a bitmask).
91///
92/// This is equivalent to multiplying the input vector by a triangular matrix
93/// where entry (i,j) is 1 if j is a subset of i (as bitmasks), and 0 otherwise.
94///
95/// # Input
96/// ## qs
97/// Boolean array of minterm coefficients of length 2^n for some integer n ≥ 0.
98///
99/// # Output
100/// Boolean array of the same length as input containing monomial coefficients.
101///
102/// # Remarks
103/// This function is the classical preprocessing step for quantum phase lookup operations,
104/// converting phase data from standard basis coefficients to power product coefficients.
105/// The transformation is its own inverse when applied twice.
106function FastMobiusTransform(coefficients : Bool[]) : Bool[] {
107 let len = Length(coefficients);
108 Fact((len &&& (len-1)) == 0, "Length of a qubit register should be a power of 2");
109 let n = BitSizeI(len)-1;
110
111 mutable result = coefficients;
112 // For each bit position (from least to most significant).
113 for i in 0..n-1 {
114 let step = 2^i;
115 // For each pair of positions that differ only in that bit.
116 for j in 0..(step * 2)..len-1 {
117 for k in 0..step-1 {
118 // XOR the "upper" position with the "lower" position.
119 result[j + k + step] = Xor(result[j + k + step], result[j + k]);
120 }
121 }
122 }
123 return result;
124}
125
126/// # Summary
127/// Measures all qubits in the `target` register in the X basis. Resets them to |0⟩.
128/// Computes and pads resulting phase data to cover the entire address space `2^address_size`.
129operation MeasureAndComputePhaseData(
130 target : Qubit[],
131 data : Bool[][],
132 address_size : Int
133) : Bool[] {
134 // Measure target register in X basis.
135 mutable measurements = [];
136 for qubit in target {
137 set measurements += [MResetX(qubit) == One];
138 }
139
140 // Get phasing data via parity checks.
141 mutable phaseData = [];
142 for x in data {
143 set phaseData += [BinaryInnerProduct(x, measurements)];
144 }
145
146 // Pad phase data at the end to cover the entire address space.
147 Padded(-2^address_size, false, phaseData)
148}
149
150/// # Summary
151/// Computes dot (inner) product of two vectors over GF(2) field.
152/// This isn't a proper inner product as it is not positive-definite.
153///
154/// It is used to see if a phase correction is needed for a bit string `data`
155/// after obtaining a measurement result `measurements`.
156function BinaryInnerProduct(data : Bool[], measurements : Bool[]) : Bool {
157 mutable sum = false;
158 for i in IndexRange(measurements) {
159 set sum = Xor(sum, (data[i] and measurements[i]));
160 }
161 sum
162}
163
164/// # Summary
165/// Combines multiple control qubits into a single control qubit using auxiliary qubits.
166/// Logarithmic depth and linear number of auxiliary qubits are used.
167operation CombineControls(controls : Qubit[], aux : Qubit[]) : Unit is Adj {
168 Fact(Length(controls) >= 1, "CombineControls: controls must not be empty.");
169 Fact(Length(controls) == Length(aux) + 1, "CombineControls: control and aux length mismatch.");
170 let combined = controls + aux;
171 let aux_offset = Length(controls);
172 for i in 0..aux_offset-2 {
173 AND(combined[i * 2], combined[i * 2 + 1], combined[aux_offset + i]);
174 }
175}
176
177/// # Summary
178/// Retrieves the combined control qubit after CombineControls operation.
179function GetCombinedControl(controls : Qubit[], aux : Qubit[]) : Qubit {
180 Fact(Length(controls) >= 1, "GetCombinedControl: controls must not be empty.");
181 Fact(Length(controls) == Length(aux) + 1, "GetCombinedControl: control and aux length mismatch.");
182 if Length(controls) == 1 {
183 return Head(controls);
184 } else {
185 return Tail(aux);
186 }
187}
188
189// =============================
190// Tests
191
192@Test()
193function TestFastMobiusTransform() : Unit {
194 // Test cases for FastMobiusTransform.
195 let testCases = [
196 ([], []),
197 ([false], [false]),
198 ([true], [true]),
199 ([false, false], [false, false]),
200 ([false, true], [false, true]),
201 ([true, false], [true, true]),
202 ([true, true], [true, false]),
203 ([false, false, false, false], [false, false, false, false]),
204 ([false, false, false, true], [false, false, false, true]),
205 ([false, false, true, false], [false, false, true, true]),
206 ([false, false, true, true], [false, false, true, false]),
207 ([true, true, true, true], [true, false, false, false]),
208 ([true, false, false, false], [true, true, true, true]),
209 ];
210 for (input, expected) in testCases {
211 let output = FastMobiusTransform(input);
212 Fact(output == expected, $"FastMobiusTransform({input}) should be {expected}, got {output}");
213 // Test that applying the transform twice returns the original input.
214 let roundTrip = FastMobiusTransform(output);
215 Fact(roundTrip == input, $"FastMobiusTransform(FastMobiusTransform({input})) should be {input}, got {roundTrip}");
216 }
217}
218
219internal operation TestCombineControlsForN(n : Int) : Unit {
220 let all_ones = 2^n - 1;
221 use controls = Qubit[n];
222 use aux = Qubit[n - 1];
223
224 // Test all combinations of control qubits.
225 for i in 0..all_ones {
226 ApplyXorInPlace(i, controls);
227
228 // Combine controls.
229 within {
230 CombineControls(controls, aux);
231 } apply {
232 let combined = GetCombinedControl(controls, aux);
233 // Check that combined control is |1⟩ iff all controls are |1⟩.
234 if i == all_ones {
235 within {
236 // Ensure combined control is |1⟩.
237 X(combined);
238 } apply {
239 Fact(CheckZero(combined), $"Combined control should be |1⟩ when all {n} controls are |1⟩.");
240 }
241 } else {
242 Fact(CheckZero(combined), $"Combined control should be |0⟩ when some of {n} controls are |0⟩.");
243 }
244 }
245 ApplyXorInPlace(i, controls);
246 // Check that all qubits are reset to |0⟩.
247 Fact(CheckAllZero(controls + aux), "All qubits should be reset to |0⟩ after CombineControls adjoint.");
248 }
249}
250
251@Test()
252operation TestCombineControls() : Unit {
253 TestCombineControlsForN(1);
254 TestCombineControlsForN(4);
255}
256