-
Notifications
You must be signed in to change notification settings - Fork 165
Description
Summary
Implement applyNonUnitaryPauliGadget() which extends applyPauliGadget() to accept a complex angle parameter. This requires merely updating several signatures in operations.cpp and localiser.cpp.
Context
The existing function applyPauliGadget() accepts a real scalar angle and a PauliStr str (a tensor product of Pauli operators), and simulates the unitary operation
This is a multi-qubit generalisation of a rotation operator and appears in many quantum circuits, like Trotter simulations.
Consider the non-unitary produced by substituting the real
Despite being non-physical, such an operation turns out to be very useful in a classical simulator. It appears in Trotter circuits for "imaginary time evolution" which when effected upon a state, drives the system toward the ground-state.
A slightly more general variant of the operation would permit the input parameter to be any complex number.
A function called applyNonUnitaryPauliGadget() which accepts a complex applyTrotterizedPauliStrSumGadget() to (among other things) simulate imaginary-time evolution.
The existing
applyPauliGadget()function has controlled-variants likeapplyMultiStateControlledPauliGadget. Such variants are not needed nor useful for the newapplyNonUnitaryPauliGadget().
Details
In QuEST, a qreal is a precision-agnostic alias for a real scalar (like double) while qcomp is a similar alias for a complex number (like std::complex<double>). This challenge involves retaining the existing function
void applyPauliGadget(Qureg qureg, PauliStr str, qreal angle);while defining a new function
void applyNonUnitaryPauliGadget(Qureg qureg, PauliStr str, qcomp angle);to simulate angle.
Fortunately, this requires no new simulation code! The existing applyPauliGadget() function eventually invokes the localiser.cpp function localiser_statevec_anyCtrlPauliGadget():
QuEST/quest/src/core/localiser.cpp
Lines 1349 to 1360 in e9fa519
| void localiser_statevec_anyCtrlPauliGadget(Qureg qureg, vector<int> ctrls, vector<int> ctrlStates, PauliStr str, qreal phase) { | |
| // when str=IZ, we must use the above bespoke algorithm | |
| if (!paulis_containsXOrY(str)) { | |
| localiser_statevec_anyCtrlPhaseGadget(qureg, ctrls, ctrlStates, paulis_getInds(str), phase); | |
| return; | |
| } | |
| qcomp ampFac = std::cos(phase); | |
| qcomp pairAmpFac = std::sin(phase) * 1_i; | |
| anyCtrlPauliTensorOrGadget(qureg, ctrls, ctrlStates, str, ampFac, pairAmpFac); | |
| } |
Setting aside the str=IZ edgecase, this merely expands exp(i phase) with Euler's formula and passes the two terms to the internal localiser function:
void anyCtrlPauliTensorOrGadget(..., qcomp ampFac, qcomp pairAmpFac);The terms (ampFac and pairAmpFac) are eventually multiplied directly upon the quantum amplitudes (like here). Since ampFac and pairAmpFac are already of type qcomp, we can trivially change the type of parameter phase in localiser_statevec_anyCtrlPauliGadget from qreal to qcomp to support complex phases, like str=IZ edgecase. Any code passing a qreal phase to these functions can instead pass qcomp(phase,0). Easy! 🎉
The actual applyNonUnitaryPauliGadget() function contents (similar to applyMultiStateControlledPauliGadget()) can be copied from below:
void applyNonUnitaryPauliGadget(Qureg qureg, PauliStr str, qcomp angle) {
validate_quregFields(qureg, __func__);
validate_pauliStrTargets(qureg, str, __func__);
qcomp phase = util_getPhaseFromGateAngle(angle);
localiser_statevec_anyCtrlPauliGadget(qureg, {}, {}, str, phase);
if (!qureg.isDensityMatrix)
return;
// conj(e^i(a)XZ) = e^(-i conj(a)XZ) but conj(Y)=-Y, so odd-Y undoes phase negation
phase = std::conj(phase) * (paulis_hasOddNumY(str) ? 1 : -1);
str = paulis_getShiftedPauliStr(str, qureg.numQubits);
localiser_statevec_anyCtrlPauliGadget(qureg, {}, {}, str, phase);
}where the utilities.cpp function util_getPhaseFromGateAngle has been given a qcomp overload.
Testing
Implementing the unit tests for this function is beyond the scope of the unitaryHACK challenge. Instead, one can locally run the script
#include "quest.h"
int main() {
initQuESTEnv();
Qureg qureg = createQureg(3);
PauliStr str = getInlinePauliStr("XYZ", {0,1,2});
qcomp angle = getQcomp(.4, .8);
initPlusState(qureg);
applyNonUnitaryPauliGadget(qureg, str, angle);
qreal norm = calcTotalProb(qureg);
reportScalar("norm", norm);
finalizeQuESTEnv();
return 0;
}which should output 1.33743 (in lieu of 1.0).
Such a test can be automatically run by QuEST's CI across varying operating systems, compilers and floating-point precisions by including the above file in PR within /examples/automated/ (saved as apply_no_unitary_pauli_gadget.c). See an explanation here.
Bonus
Once applyNonUnitaryPauliGadget() is implemented, it will be trivial to also generalise applyTrotterizedPauliStrSumGadget() to applyTrotterizedNonUnitaryPauliStrSumGadget() which accepts a complex angle.