-
Notifications
You must be signed in to change notification settings - Fork 160
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
.