microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/python-cli

Branches

Tags

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

Clone

HTTPS

Download ZIP

samples/estimation/df-chemistry/src/df_chemistry.qs

359lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3namespace Microsoft.Quantum.Applications.Chemistry {
4 open Microsoft.Quantum.Arrays;
5 open Microsoft.Quantum.Canon;
6 open Microsoft.Quantum.Convert;
7 open Microsoft.Quantum.Diagnostics;
8 open Microsoft.Quantum.Math;
9 open Microsoft.Quantum.ResourceEstimation;
10 open Microsoft.Quantum.Unstable.Arithmetic;
11 open Microsoft.Quantum.Unstable.TableLookup;
12
13 // ------------------------------------------ //
14 // DF chemistry (public operations and types) //
15 // ------------------------------------------ //
16
17 /// # Summary
18 /// The description of a double-factorized Hamiltonian
19 ///
20 /// # Reference
21 /// arXiv:2007.14460, p. 9, eq. 9
22 newtype DoubleFactorizedChemistryProblem = (
23 // Number of orbitals (N, p. 8)
24 NumOrbitals : Int,
25 // one-body norm (ǁL⁽⁻¹⁾ǁ, p. 8, eq. 16)
26 OneBodyNorm : Double,
27 // one-body norm (¼∑ǁL⁽ʳ⁾ǁ², p. 8, eq. 16)
28 TwoBodyNorm : Double,
29 // eigenvalues in the EVD of the one-electron Hamiltonian (λₖ, p. 54, eq. 67)
30 OneBodyEigenValues : Double[],
31 // eigenvectors in the EVD of the one-electron Hamiltonian (Rₖ, p. 54, eq. 67)
32 OneBodyEigenVectors : Double[][],
33 // norms inside Λ_SH (p. 56, eq. 77)
34 Lambdas : Double[],
35 // eigenvalues in the EVDs of the two-electron Hamiltonian for all r (λₖ⁽ʳ⁾, p. 56, eq. 77)
36 TwoBodyEigenValues : Double[][],
37 // eigenvectors in the EVDs of the two-electron Hamiltonian for all r (R⁽ʳ⁾ₖ, p. 56, eq. 77)
38 TwoBodyEigenVectors : Double[][][],
39 );
40
41 newtype DoubleFactorizedChemistryParameters = (
42 // Standard deviation (ΔE, p. 8, eq. 1)
43 // Typically set to 0.001
44 StandardDeviation : Double,
45 );
46
47 /// # Summary
48 /// Performs quantum circuit in (p. 45, eq. 33) for double-factorized
49 /// Hamiltonian; also prepares phase gradient registers to use phase
50 /// gradient technique (p. 55)
51 operation DoubleFactorizedChemistry(
52 problem : DoubleFactorizedChemistryProblem,
53 parameters : DoubleFactorizedChemistryParameters
54 ) : Unit {
55 let constants = ComputeConstants(problem, parameters);
56
57 use register0 = Qubit[problem::NumOrbitals];
58 use register1 = Qubit[problem::NumOrbitals];
59 use phaseGradientRegister = Qubit[constants::RotationAngleBitPrecision];
60
61 // compute number of repetitions
62 let norm = problem::OneBodyNorm + problem::TwoBodyNorm;
63 let repetitions = Ceiling((PI() * norm) / (2.0 * parameters::StandardDeviation));
64
65 let walkStep = MakeWalkStep(problem, constants);
66 use walkStepHelper = Qubit[walkStep::NGarbageQubits];
67
68 within {
69 X(phaseGradientRegister[0]);
70 Adjoint ApplyQFT(Reversed(phaseGradientRegister));
71 } apply {
72 within {
73 RepeatEstimates(repetitions);
74 } apply {
75 walkStep::StepOp(register0, register1, phaseGradientRegister, walkStepHelper);
76 }
77 }
78 }
79
80 // -------------------------------------------- //
81 // DF chemistry (internal operations and types) //
82 // -------------------------------------------- //
83
84 /// # Summary
85 /// Constants that are used in the implementation of the one- and two-
86 /// electron operators, which are computed from the double factorized
87 /// problem and parameters.
88 internal newtype DoubleFactorizedChemistryConstants = (
89 RotationAngleBitPrecision : Int,
90 StatePreparationBitPrecision : Int,
91 TargetError : Double
92 );
93
94 internal function ComputeConstants(
95 problem : DoubleFactorizedChemistryProblem,
96 parameters : DoubleFactorizedChemistryParameters
97 ) : DoubleFactorizedChemistryConstants {
98 let pj = 0.1;
99 let barEpsilon = Sqrt(pj);
100 let norm = problem::OneBodyNorm + problem::TwoBodyNorm;
101 let epsilon = 0.1 * parameters::StandardDeviation / norm;
102
103 let RotationAngleBitPrecision = Ceiling(1.152 + Lg(Sqrt((IntAsDouble((problem::NumOrbitals - 1) * 8) * PI() * norm) / parameters::StandardDeviation)) + 0.5 * Lg(1.0 / barEpsilon));
104 let StatePreparationBitPrecision = Ceiling(Lg(1.0 / epsilon) + 2.5);
105 let TargetError = 2.0^IntAsDouble(1 - StatePreparationBitPrecision);
106
107 DoubleFactorizedChemistryConstants(
108 RotationAngleBitPrecision,
109 StatePreparationBitPrecision,
110 TargetError
111 )
112 }
113
114 internal newtype WalkStep = (
115 NGarbageQubits : Int,
116 StepOp : (Qubit[], Qubit[], Qubit[], Qubit[]) => Unit
117 );
118
119 /// # Summary
120 /// Performs one B[H] and reflection (p. 45, eq. 33)
121 internal function MakeWalkStep(
122 problem : DoubleFactorizedChemistryProblem,
123 constants : DoubleFactorizedChemistryConstants
124 ) : WalkStep {
125 let oneElectronOperator = MakeOneElectronOperator(problem::OneBodyEigenValues, problem::OneBodyEigenVectors, constants);
126 let twoElectronOperator = MakeTwoElectronOperator(problem, constants);
127
128 let NGarbageQubits = oneElectronOperator::NGarbageQubits + twoElectronOperator::NGarbageQubits;
129
130 WalkStep(NGarbageQubits, WalkStepOperation(problem, oneElectronOperator, twoElectronOperator, _, _, _, _))
131 }
132
133 internal operation WalkStepOperation(
134 problem : DoubleFactorizedChemistryProblem,
135 oneElectronOperator : OneElectronOperator,
136 twoElectronOperator : TwoElectronOperator,
137 register0 : Qubit[],
138 register1 : Qubit[],
139 phaseGradientRegister : Qubit[],
140 helper : Qubit[]
141 ) : Unit {
142 use ctl = Qubit();
143
144 let helperParts = Partitioned([oneElectronOperator::NGarbageQubits, twoElectronOperator::NGarbageQubits], helper);
145 Fact(IsEmpty(Tail(helperParts)), "wrong number of helper qubits in WalkStepOperation");
146
147 // apply Hamiltionian
148 within {
149 PrepareSingleQubit(problem::OneBodyNorm, problem::TwoBodyNorm, ctl);
150 } apply {
151 within { X(ctl); } apply {
152 Controlled oneElectronOperator::Apply([ctl], (register0, register1, phaseGradientRegister, [], helperParts[0]));
153 }
154 Controlled twoElectronOperator::Apply([ctl], (register0, register1, phaseGradientRegister, helperParts[1]));
155 }
156
157 // reflection
158 ReflectAboutInteger(0, helper);
159 }
160
161 internal newtype OneElectronOperator = (
162 NGarbageQubits : Int,
163 Apply : (Qubit[], Qubit[], Qubit[], Qubit[], Qubit[]) => Unit is Adj + Ctl
164 );
165
166 /// # Summary
167 /// Performs quantum circuit for one-electron operator (p. 54, eq. 70)
168 internal function MakeOneElectronOperator(
169 eigenValues : Double[],
170 eigenVectors : Double[][],
171 constants : DoubleFactorizedChemistryConstants
172 ) : OneElectronOperator {
173 let data = Mapped(eigenValue -> [eigenValue < 0.0], eigenValues);
174 let prepare = MakePrepareArbitrarySuperpositionWithData(constants::TargetError, eigenValues, data);
175
176 OneElectronOperator(
177 prepare::NGarbageQubits,
178 OneElectronOperatorOperation(eigenVectors, constants, prepare, _, _, _, _, _)
179 )
180 }
181
182 internal operation OneElectronOperatorOperation(
183 eigenVectors : Double[][],
184 constants : DoubleFactorizedChemistryConstants,
185 prepare : PrepareArbitrarySuperposition,
186 register0 : Qubit[],
187 register1 : Qubit[],
188 phaseGradientRegister : Qubit[],
189 offsets : Qubit[],
190 helper : Qubit[]
191 ) : Unit is Adj + Ctl {
192 if BeginEstimateCaching("OneElectronOperator", IsEmpty(offsets) ? 0 | 1) {
193 // assertions
194 Fact(Length(helper) == prepare::NGarbageQubits, "invalid number of helper qubits in OneElectronOperatorOperation");
195
196 let precision = constants::RotationAngleBitPrecision;
197 let bitstrings = AllEigenVectorsAsBitString(eigenVectors, precision);
198
199 use prepQubits = Qubit[prepare::NIndexQubits];
200 use rotationQubits = Qubit[Length(Head(bitstrings))];
201 use sign = Qubit();
202 use spin = Qubit();
203
204 within {
205 if not IsEmpty(offsets) {
206 prepare::PrepareWithSelect(SelectWithOffset(_, offsets, _, _), prepQubits, [sign], helper);
207 } else {
208 prepare::Prepare(prepQubits, [sign], helper);
209 }
210 H(spin);
211 for i in IndexRange(register0) {
212 Controlled SWAP([spin], (register0[i], register1[i]));
213 }
214 if not IsEmpty(offsets) {
215 RippleCarryCGIncByLE(offsets, prepQubits);
216 }
217 Select(bitstrings, prepQubits, rotationQubits);
218 } apply {
219 ApplyGivensRotations(phaseGradientRegister, Chunks(precision, rotationQubits), register0);
220 Z(sign);
221 }
222
223 EndEstimateCaching();
224 }
225 }
226
227 internal operation SelectWithOffset(data : Bool[][], offset : Qubit[], address : Qubit[], target : Qubit[]) : Unit is Adj + Ctl {
228 within {
229 RippleCarryCGIncByLE(offset, address);
230 } apply {
231 Select(data, address, target);
232 }
233 }
234
235 internal newtype TwoElectronOperator = (
236 NGarbageQubits : Int,
237 Apply : (Qubit[], Qubit[], Qubit[], Qubit[]) => Unit is Ctl
238 );
239
240 /// # Summary
241 /// Performs quantum circuit for two-electron operator (p. 57, eq. 78)
242 internal function MakeTwoElectronOperator(problem : DoubleFactorizedChemistryProblem, constants : DoubleFactorizedChemistryConstants) : TwoElectronOperator {
243 let lambdaPrepare = MakePrepareArbitrarySuperposition(
244 constants::TargetError,
245 problem::Lambdas
246 );
247
248 let eigenValuesFlattened = Flattened(problem::TwoBodyEigenValues);
249 let eigenVectorsFlattened = Flattened(problem::TwoBodyEigenVectors);
250 let oneElectronOperator = MakeOneElectronOperator(eigenValuesFlattened, eigenVectorsFlattened, constants);
251
252 let numGarbageQubits = lambdaPrepare::NGarbageQubits + oneElectronOperator::NGarbageQubits;
253
254 TwoElectronOperator(numGarbageQubits, TwoElectronOperatorOperation(problem, lambdaPrepare, oneElectronOperator, _, _, _, _))
255 }
256
257 internal operation TwoElectronOperatorOperation(
258 problem : DoubleFactorizedChemistryProblem,
259 lambdaPrepare : PrepareArbitrarySuperposition,
260 oneElectronOperator : OneElectronOperator,
261 register0 : Qubit[],
262 register1 : Qubit[],
263 phaseGradientRegister : Qubit[],
264 helper : Qubit[]
265 ) : Unit is Ctl {
266 let helperParts = Partitioned([lambdaPrepare::NGarbageQubits], helper);
267 let dataOffsets = ComputeOffsetDataSet(Mapped(Length, problem::TwoBodyEigenValues));
268
269 use rankQubits = Qubit[lambdaPrepare::NIndexQubits];
270 use offsetQubits = Qubit[Length(dataOffsets[0])];
271
272 within {
273 lambdaPrepare::Prepare(rankQubits, [], helperParts[0]);
274 Select(dataOffsets, rankQubits, offsetQubits);
275 oneElectronOperator::Apply(register0, register1, phaseGradientRegister, offsetQubits, helperParts[1]);
276 } apply {
277 ReflectAboutInteger(0, helperParts[1]);
278 }
279 }
280
281 internal function ComputeOffsetDataSet(lengths : Int[]) : Bool[][] {
282 mutable currentOffset = 0;
283 mutable offsets = [0, size = Length(lengths)];
284
285 for i in IndexRange(lengths) {
286 set offsets w/= i <- currentOffset;
287 set currentOffset += lengths[i];
288 }
289
290 let offsetWidth = Ceiling(Lg(IntAsDouble(currentOffset)));
291 return Mapped(IntAsBoolArray(_, offsetWidth), offsets);
292 }
293
294 /// # Summary
295 /// Performs quantum circuit for phase rotations (p. 54, eq. 73)
296 internal operation ApplyGivensRotations(pgr : Qubit[], rotationQubits : Qubit[][], target : Qubit[]) : Unit is Adj + Ctl {
297 within {
298 let windows = Windows(2, target);
299 for i in RangeReverse(IndexRange(rotationQubits)) {
300 let rotationControls = rotationQubits[i];
301 let qs = windows[i];
302 ApplyWithMajoranaCliffords(1, ApplyRotationUsingRippleCarryAddition(pgr, rotationControls, _), qs);
303 ApplyWithMajoranaCliffords(0, Adjoint ApplyRotationUsingRippleCarryAddition(pgr, rotationControls, _), qs);
304 }
305 } apply {
306 X(Head(target));
307 Y(Head(target));
308 }
309 }
310
311 internal operation ApplyRotationUsingRippleCarryAddition(pgr : Qubit[], rotationQubits : Qubit[], target : Qubit) : Unit is Adj {
312 Controlled Adjoint RippleCarryCGIncByLE([target], (Reversed(Rest(rotationQubits)), pgr));
313 }
314
315 internal operation ApplyWithMajoranaCliffords(x : Int, op : (Qubit => Unit is Adj), qs : Qubit[]) : Unit is Adj {
316 // x is the Majorana type (which can be either 0 or 1, see p. 52, eq. 59)
317 within {
318 Adjoint S(qs[x]);
319 H(qs[x]);
320 H(qs[1 - x]);
321 CNOT(qs[1], qs[0]);
322 } apply {
323 op(qs[0]);
324 }
325 }
326
327 internal function AllEigenVectorsAsBitString(eigenVectors : Double[][], precision : Int) : Bool[][] {
328 mutable bitstrings = [];
329
330 let tau = 2.0 * PI();
331 let preFactor = 2.0^IntAsDouble(precision);
332
333 for eigenVector in eigenVectors {
334 // Computes rotation angles for Majorana operator ($\vec u$ in p. 52, eq. 55)
335 mutable result = [];
336 mutable sins = 1.0;
337
338 for index in 0..Length(eigenVector) - 2 {
339 // We apply MinD, such that rounding errors do not lead to
340 // an argument for ArcCos which is larger than 1.0. (p. 52, eq. 56)
341 let theta = sins == 0.0 ? 0.0 | 0.5 * ArcCos(MinD(eigenVector[index] / sins, 1.0));
342
343 // all angles as bit string
344 let factor = theta / tau;
345 set result += Reversed(IntAsBoolArray(IsNaN(factor) ? 0 | Floor(preFactor * factor), precision));
346
347 set sins *= Sin(2.0 * theta);
348 }
349
350 set bitstrings += [result];
351 }
352
353 bitstrings
354 }
355
356 internal function IsNaN(value : Double) : Bool {
357 value != value
358 }
359}
360