Skip to content

Commit 6eec1cf

Browse files
added Trotterised time evolution (#674)
Specifically: - applyTrotterizedUnitaryTimeEvolution() - applyTrotterizedImaginaryTimeEvolution() - applyTrotterizedNoisyTimeEvolution() where the latter has significant novelty. PR also - (patch) made imaginary-time evolution assert Hermiticity (315ea41) - (patch) patched non-unitary Trotter on density matrix (01c51e1) - updated dynamics examples to use these new time-evol functions - tidied some Pauli algebra (replacing paulis_hasOddNumY calls with direct paulis_getSignOfPauliStrConj) - made createPauliStrSum validate it can fit in RAM
1 parent 2db00e5 commit 6eec1cf

File tree

18 files changed

+1064
-162
lines changed

18 files changed

+1064
-162
lines changed

examples/extended/dynamics.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/** @file
2-
* An example of using QuEST (primarily function
3-
* applyTrotterizedPauliStrSumGadget()) to perform
2+
* An example of using QuEST to perform closed
43
* dynamical simulation via Trotterisation of the
54
* unitary-time evolution operator.
65
*
@@ -158,7 +157,7 @@ int main() {
158157
for (int i=0; i<steps; i++) {
159158

160159
// evolve qureg under (approx) exp(-i dt H)
161-
applyTrotterizedPauliStrSumGadget(qureg, hamil, -dt, order, reps);
160+
applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt, order, reps);
162161

163162
// calculate and report <O>
164163
qreal time = dt * (i+1);
@@ -188,7 +187,7 @@ int main() {
188187

189188
// verify results by uninterrupted higher-order simulation to target time
190189
initPlusState(qureg);
191-
applyTrotterizedPauliStrSumGadget(qureg, hamil, -dt*steps, order+2, reps*steps);
190+
applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt*steps, order+2, reps*steps);
192191
reportScalar("final <O>", calcExpecPauliStrSum(qureg, observ));
193192

194193
// clean up

examples/extended/dynamics.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/** @file
2-
* An example of using QuEST (primarily function
3-
* applyTrotterizedPauliStrSumGadget()) to perform
2+
* An example of using QuEST to perform closed
43
* dynamical simulation via Trotterisation of the
54
* unitary-time evolution operator.
65
*
@@ -155,7 +154,7 @@ int main() {
155154
for (int i=0; i<steps; i++) {
156155

157156
// evolve qureg under (approx) exp(-i dt H)
158-
applyTrotterizedPauliStrSumGadget(qureg, hamil, -dt, order, reps);
157+
applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt, order, reps);
159158

160159
// calculate and report <O>
161160
qreal time = dt * (i+1);
@@ -182,7 +181,7 @@ int main() {
182181

183182
// verify results by uninterrupted higher-order simulation to target time
184183
initPlusState(qureg);
185-
applyTrotterizedPauliStrSumGadget(qureg, hamil, -dt*steps, order+2, reps*steps);
184+
applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt*steps, order+2, reps*steps);
186185
reportScalar("final <O>", calcExpecPauliStrSum(qureg, observ));
187186

188187
// clean up

quest/include/trotterisation.h

Lines changed: 443 additions & 89 deletions
Large diffs are not rendered by default.

quest/src/api/operations.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ using std::vector;
3030
* PRVIATE UTILITIES
3131
*/
3232

33+
extern int paulis_getSignOfPauliStrConj(PauliStr str);
34+
3335
extern bool paulis_isIdentity(PauliStr str);
34-
extern bool paulis_hasOddNumY(PauliStr str);
3536
extern PauliStr paulis_getShiftedPauliStr(PauliStr str, int pauliShift);
3637
extern PauliStr paulis_getKetAndBraPauliStr(PauliStr str, Qureg qureg);
3738

@@ -966,7 +967,7 @@ void applyMultiStateControlledPauliStr(Qureg qureg, int* controls, int* states,
966967
// operation sinto a single tensor, i.e. +- (shift(str) (x) str), to
967968
// avoid superfluous re-enumeration of the state
968969
if (qureg.isDensityMatrix && numControls == 0) {
969-
factor = paulis_hasOddNumY(str)? -1 : 1;
970+
factor = paulis_getSignOfPauliStrConj(str);
970971
ctrlVec = util_getConcatenated(ctrlVec, util_getBraQubits(ctrlVec, qureg));
971972
stateVec = util_getConcatenated(stateVec, stateVec);
972973
str = paulis_getKetAndBraPauliStr(str, qureg);
@@ -976,7 +977,7 @@ void applyMultiStateControlledPauliStr(Qureg qureg, int* controls, int* states,
976977

977978
// but density-matrix control qubits require two distinct operations
978979
if (qureg.isDensityMatrix && numControls > 0) {
979-
factor = paulis_hasOddNumY(str)? -1 : 1;
980+
factor = paulis_getSignOfPauliStrConj(str);
980981
ctrlVec = util_getBraQubits(ctrlVec, qureg);
981982
str = paulis_getShiftedPauliStr(str, qureg.numQubits);
982983
localiser_statevec_anyCtrlPauliTensor(qureg, ctrlVec, stateVec, str, factor);
@@ -1230,8 +1231,8 @@ void applyNonUnitaryPauliGadget(Qureg qureg, PauliStr str, qcomp angle) {
12301231
if (!qureg.isDensityMatrix)
12311232
return;
12321233

1233-
// conj(e^i(a)XZ) = e^(-i conj(a)XZ) but conj(Y)=-Y, so odd-Y undoes phase negation
1234-
phase = std::conj(phase) * (paulis_hasOddNumY(str) ? 1 : -1);
1234+
// conj(e^i(a)P) = e^(-i s conj(a) P)
1235+
phase = - std::conj(phase) * paulis_getSignOfPauliStrConj(str);
12351236
str = paulis_getShiftedPauliStr(str, qureg.numQubits);
12361237
localiser_statevec_anyCtrlPauliGadget(qureg, {}, {}, str, phase);
12371238
}
@@ -1273,8 +1274,8 @@ void applyMultiStateControlledPauliGadget(Qureg qureg, int* controls, int* state
12731274
if (!qureg.isDensityMatrix)
12741275
return;
12751276

1276-
// conj(e^iXZ) = e^(-iXZ), but conj(Y)=-Y, so odd-Y undoes phase negation
1277-
phase *= paulis_hasOddNumY(str) ? 1 : -1;
1277+
// conj(e^(i a P)) = e^(-i s a P)
1278+
phase *= - paulis_getSignOfPauliStrConj(str);
12781279
ctrlVec = util_getBraQubits(ctrlVec, qureg);
12791280
str = paulis_getShiftedPauliStr(str, qureg.numQubits);
12801281
localiser_statevec_anyCtrlPauliGadget(qureg, ctrlVec, stateVec, str, phase);

quest/src/api/paulis.cpp

Lines changed: 151 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "quest/src/comm/comm_routines.hpp"
2121

2222
#include <iostream>
23+
#include <utility>
2324
#include <vector>
2425
#include <string>
2526
#include <array>
@@ -87,7 +88,7 @@ void freeAllMemoryIfAnyAllocsFailed(PauliStrSum sum) {
8788

8889

8990
/*
90-
* INTERNAL UTILITIES
91+
* INTERNAL PauliStr UTILITIES
9192
*
9293
* callable by other internal files but which are not exposed in the header
9394
* because we do not wish to make them visible to users. Ergo other internal
@@ -139,12 +140,6 @@ int paulis_getIndOfLefmostNonIdentityPauli(PauliStr* strings, qindex numStrings)
139140
}
140141

141142

142-
int paulis_getIndOfLefmostNonIdentityPauli(PauliStrSum sum) {
143-
144-
return paulis_getIndOfLefmostNonIdentityPauli(sum.strings, sum.numTerms);
145-
}
146-
147-
148143
bool paulis_containsXOrY(PauliStr str) {
149144

150145
int maxInd = paulis_getIndOfLefmostNonIdentityPauli(str);
@@ -160,16 +155,6 @@ bool paulis_containsXOrY(PauliStr str) {
160155
}
161156

162157

163-
bool paulis_containsXOrY(PauliStrSum sum) {
164-
165-
for (qindex i=0; i<sum.numTerms; i++)
166-
if (paulis_containsXOrY(sum.strings[i]))
167-
return true;
168-
169-
return false;
170-
}
171-
172-
173158
bool paulis_hasOddNumY(PauliStr str) {
174159

175160
bool odd = false;
@@ -182,6 +167,13 @@ bool paulis_hasOddNumY(PauliStr str) {
182167
}
183168

184169

170+
int paulis_getSignOfPauliStrConj(PauliStr str) {
171+
172+
// conj(Y) = -Y, conj(YY) = YY
173+
return paulis_hasOddNumY(str)? -1 : 1;
174+
}
175+
176+
185177
int paulis_getPrefixZSign(Qureg qureg, vector<int> prefixZ) {
186178

187179
int sign = 1;
@@ -239,7 +231,7 @@ qindex paulis_getTargetBitMask(PauliStr str) {
239231
}
240232

241233

242-
array<vector<int>,3> paulis_getSeparateInds(PauliStr str, Qureg qureg) {
234+
array<vector<int>,3> paulis_getSeparateInds(PauliStr str) {
243235

244236
vector<int> iXYZ = paulis_getTargetInds(str);
245237
vector<int> iX, iY, iZ;
@@ -279,18 +271,25 @@ PauliStr paulis_getShiftedPauliStr(PauliStr str, int pauliShift) {
279271
}
280272

281273

282-
PauliStr paulis_getKetAndBraPauliStr(PauliStr str, Qureg qureg) {
274+
PauliStr paulis_getTensorProdOfPauliStr(PauliStr left, PauliStr right, int numQubits) {
275+
276+
// computes left (tensor) right, assuming right is smaller than numQubits
277+
PauliStr shifted = paulis_getShiftedPauliStr(left, numQubits);
283278

284-
PauliStr shifted = paulis_getShiftedPauliStr(str, qureg.numQubits);
285-
286279
// return a new stack PauliStr instance (avoiding C++20 initialiser)
287280
PauliStr out;
288-
out.lowPaulis = str.lowPaulis | shifted.lowPaulis;
289-
out.highPaulis = str.highPaulis | shifted.highPaulis;
281+
out.lowPaulis = right.lowPaulis | shifted.lowPaulis;
282+
out.highPaulis = right.highPaulis | shifted.highPaulis;
290283
return out;
291284
}
292285

293286

287+
PauliStr paulis_getKetAndBraPauliStr(PauliStr str, Qureg qureg) {
288+
289+
return paulis_getTensorProdOfPauliStr(str, str, qureg.numQubits);
290+
}
291+
292+
294293
PAULI_MASK_TYPE paulis_getKeyOfSameMixedAmpsGroup(PauliStr str) {
295294

296295
PAULI_MASK_TYPE key = 0;
@@ -312,6 +311,54 @@ PAULI_MASK_TYPE paulis_getKeyOfSameMixedAmpsGroup(PauliStr str) {
312311
}
313312

314313

314+
std::pair<qcomp,PauliStr> paulis_getPauliStrProd(PauliStr strA, PauliStr strB) {
315+
316+
// a . b = coeff * (a ^ b)
317+
PauliStr strOut;
318+
strOut.lowPaulis = strA.lowPaulis ^ strB.lowPaulis;
319+
strOut.highPaulis = strA.highPaulis ^ strB.highPaulis;
320+
321+
// coeff = product of single-site product coeffs
322+
qcomp coeff = 1;
323+
for (int i=0; i<MAX_NUM_PAULIS_PER_STR; i++) {
324+
int pA = paulis_getPauliAt(strA, i);
325+
int pB = paulis_getPauliAt(strB, i);
326+
327+
// I.P = P.I = P and P.P = I contribute factor=1
328+
if (pA == 0 || pB == 0 || pA == pB)
329+
continue;
330+
331+
// XY,YZ,ZX=i, XZ,YX,ZY=-i
332+
int dif = pB - pA;
333+
coeff *= qcomp(0, (dif == 1 || dif == -2)? 1 : -1);
334+
}
335+
336+
return {coeff, strOut};
337+
}
338+
339+
340+
341+
/*
342+
* INTERNAL PauliStrSum UTILITIES
343+
*/
344+
345+
346+
int paulis_getIndOfLefmostNonIdentityPauli(PauliStrSum sum) {
347+
348+
return paulis_getIndOfLefmostNonIdentityPauli(sum.strings, sum.numTerms);
349+
}
350+
351+
352+
bool paulis_containsXOrY(PauliStrSum sum) {
353+
354+
for (qindex i=0; i<sum.numTerms; i++)
355+
if (paulis_containsXOrY(sum.strings[i]))
356+
return true;
357+
358+
return false;
359+
}
360+
361+
315362
qindex paulis_getTargetBitMask(PauliStrSum sum) {
316363

317364
qindex mask = 0;
@@ -324,6 +371,87 @@ qindex paulis_getTargetBitMask(PauliStrSum sum) {
324371
}
325372

326373

374+
void paulis_setPauliStrSumToScaledTensorProdOfConjWithSelf(PauliStrSum out, qreal factor, PauliStrSum in, int numQubits) {
375+
376+
// sets out = factor * conj(in) (x) in, where in has dim of numQubits
377+
if (paulis_getIndOfLefmostNonIdentityPauli(in) >= numQubits)
378+
error_pauliStrSumHasMoreQubitsThanSpecifiedInTensorProd();
379+
if (out.numTerms != in.numTerms * in.numTerms)
380+
error_pauliStrSumTensorProdHasIncorrectNumTerms();
381+
382+
// conj(in) (x) in = sum_jk conj(c_j) c_k conj(P_j) (x) P_k...
383+
qindex i = 0;
384+
for (qindex j=0; j<in.numTerms; j++) {
385+
for (qindex k=0; k<in.numTerms; k++) {
386+
387+
// ... where conj(P_j) = sign_j P_j
388+
out.strings[i] = paulis_getTensorProdOfPauliStr(in.strings[j], in.strings[k], numQubits);
389+
out.coeffs[i] = factor * std::conj(in.coeffs[j]) * in.coeffs[k] * paulis_getSignOfPauliStrConj(in.strings[j]);
390+
i++;
391+
}
392+
}
393+
}
394+
395+
396+
qindex paulis_getNumTermsInPauliStrSumProdOfAdjointWithSelf(PauliStrSum in) {
397+
398+
// adj(in).in has fewer terms than the numTerms^2 bound, since
399+
// a.a = I (causing -n and +1 below) and a.b ~ b.a (causing /2);
400+
// we do not however consider any cancellations of coefficients
401+
int n = in.numTerms;
402+
return 1 + (n*n - n)/2;
403+
}
404+
405+
406+
void paulis_setPauliStrSumToScaledProdOfAdjointWithSelf(PauliStrSum out, qreal factor, PauliStrSum in) {
407+
408+
// sets out = factor * adj(in) . in, permitting duplicate strings
409+
if (out.numTerms != paulis_getNumTermsInPauliStrSumProdOfAdjointWithSelf(in))
410+
error_pauliStrSumProdHasIncorrectNumTerms();
411+
412+
// since out definitely contains an identity (when neglecting coeff cancellation)
413+
// which is contributed toward by all j=k iterations below, we keep it at i=0
414+
out.strings[0] = getPauliStr("I");
415+
out.coeffs[0] = 0;
416+
qindex i = 1;
417+
418+
// we leverage that sum_jk a_j^* a_k P_j P_k...
419+
for (qindex j=0; j<in.numTerms; j++) {
420+
421+
// = sum_j ( |a_j|^2 Id + sum_k<j ...)
422+
out.coeffs[0] += factor * std::norm(in.coeffs[j]);
423+
424+
// containing sum_k<j (a_j^* a_k P_j P_k + a_k^* a_j P_k P_j)
425+
for (qindex k=0; k<j; k++) {
426+
427+
// = (a_j^* a_k b_jk + a_k^* a_j b_jk^*) P'
428+
auto [coeff, str] = paulis_getPauliStrProd(in.strings[j], in.strings[k]);
429+
430+
// = (x + x^*) P' = 2 Re[x] P'
431+
out.strings[i] = str;
432+
out.coeffs[i] = factor * 2 * std::real(std::conj(in.coeffs[j]) * in.coeffs[k] * coeff);
433+
i++;
434+
}
435+
}
436+
}
437+
438+
439+
void paulis_setPauliStrSumToShiftedConj(PauliStrSum out, PauliStrSum in, int numQubits) {
440+
441+
// sets out = conj(in) (x) I
442+
if (paulis_getIndOfLefmostNonIdentityPauli(in) >= numQubits)
443+
error_pauliStrSumHasMoreQubitsThanSpecifiedInConjShift();
444+
if (out.numTerms != in.numTerms)
445+
error_pauliStrSumConjHasIncorrectNumTerms();
446+
447+
// where conj(c P) = conj(c) sign P
448+
for (qindex i=0; i<out.numTerms; i++) {
449+
out.strings[i] = paulis_getShiftedPauliStr(in.strings[i], numQubits);
450+
out.coeffs[i] = std::conj(in.coeffs[i]) * paulis_getSignOfPauliStrConj(in.strings[i]);
451+
}
452+
}
453+
454+
327455

328456
/*
329457
* PAULI STRING INITIALISATION

0 commit comments

Comments
 (0)