Skip to content

Commit 64c7319

Browse files
committed
added multiControlledMultiRotatePauli
1 parent df5e5bc commit 64c7319

File tree

5 files changed

+328
-0
lines changed

5 files changed

+328
-0
lines changed

QuEST/include/QuEST.h

+101
Original file line numberDiff line numberDiff line change
@@ -4027,6 +4027,107 @@ void multiRotatePauli(Qureg qureg, int* targetQubits, enum pauliOpType* targetPa
40274027
*/
40284028
void multiControlledMultiRotateZ(Qureg qureg, int* controlQubits, int numControls, int* targetQubits, int numTargets, qreal angle);
40294029

4030+
/** Apply a multi-controlled multi-target multi-Pauli rotation, also known as a
4031+
* controlled Pauli gadget.
4032+
* This is the unitary
4033+
* \f[
4034+
* |1\rangle\langle 1|^{\otimes\, \text{numControls}} \; \otimes \,
4035+
* \exp \left( - i \, \frac{\theta}{2} \; \bigotimes_{j}^{\text{numTargets}} \hat{\sigma}_j\right)
4036+
* \;\;+\;\; \sum\limits_{k=0}^{2^{\,\text{numControls}} - 2} |k\rangle\langle k| \otimes \text{I}
4037+
* \f]
4038+
* where \f$\hat{\sigma}_j\f$ are the Pauli operators (::pauliOpType) in `targetPaulis`, which operate
4039+
* upon the corresponding qubits in `targetQubits`.
4040+
*
4041+
\f[
4042+
\begin{tikzpicture}[scale=.5]
4043+
\node[draw=none] at (-4, 1) {targets};
4044+
\node[draw=none] at (-4, 5) {controls};
4045+
4046+
\node[draw=none] at (0, 8) {$\vdots$};
4047+
\draw (0, 7) -- (0, 6);
4048+
4049+
\draw (-2.5, 6) -- (2.5, 6);
4050+
\draw[fill=black] (0, 6) circle (.2);
4051+
\draw (0, 6) -- (0, 4);
4052+
4053+
\draw (-2.5, 4) -- (2.5, 4);
4054+
\draw[fill=black] (0, 4) circle (.2);
4055+
\draw(0, 4) -- (0, 3);
4056+
4057+
\draw (-2.5,0) -- (-1.5, 0);
4058+
\draw (1.5, 0) -- (2.5, 0);
4059+
\draw (-2.5,2) -- (-1.5, 2);
4060+
\draw (1.5, 2) -- (2.5, 2);
4061+
\draw (-1.5,-1)--(-1.5,3)--(1.5,3)--(1.5,-1);
4062+
\node[draw=none] at (0, 1) {$e^{-i\frac{\theta}{2} \bigotimes\limits_j \hat{\sigma}_j }$};
4063+
\node[draw=none] at (0, -1) {$\vdots$};
4064+
4065+
\end{tikzpicture}
4066+
\f]
4067+
*
4068+
* > All qubits not appearing in \p targetQubits and \p controlQubits are assumed to receive the identity operator.
4069+
*
4070+
* For example:
4071+
* ```
4072+
* int numCtrls = 1;
4073+
* int numTargs = 4;
4074+
* int ctrls[] = {4};
4075+
* int targs[] = {0,1,2,3};
4076+
*
4077+
* pauliOpType paulis[] = {PAULI_X, PAULI_Y, PAULI_Z, PAULI_I};
4078+
*
4079+
* multiControlledMultiRotatePauli(
4080+
* qureg, ctrls, numCtrls, targs, paulis, numTargs, 0.1);
4081+
* ```
4082+
* effects
4083+
* \f[
4084+
* |1\rangle\langle 1 | \otimes \exp\left( -i \, (0.1/2) \, X_0 \, Y_1 \, Z_2 \right) \, \text{I}_3
4085+
* \;\; + \;\; |0\rangle\langle 0| \otimes \text{I}^{\otimes 4}
4086+
* \f]
4087+
* on \p qureg, where unspecified qubits (along with those targeted by `PAULI_I`) are
4088+
* assumed to receive the identity operator (excluded from exponentiation).
4089+
*
4090+
* > This means specifying `PAULI_I` does *not* induce a global phase factor \f$\exp(-i \theta/2)\f$.
4091+
* > Hence, if all \p targetPaulis are identity, then this function does nothing to \p qureg.
4092+
* > Specifying `PAULI_I` on a qubit is superfluous but allowed for convenience.
4093+
*
4094+
* This function effects the controlled Pauli gadget by first (controlled)
4095+
* rotating the qubits which are targeted with either `X` or `Y` into alternate basis,
4096+
* performing multiControlledMultiRotateZ() on all target qubits, then restoring
4097+
* the original basis.
4098+
*
4099+
* @see
4100+
* - multiControlledMultiRotateZ()
4101+
* - multiRotatePauli()
4102+
* - multiRotateZ()
4103+
* - rotateX()
4104+
* - rotateY()
4105+
* - rotateZ()
4106+
* - rotateAroundAxis()
4107+
*
4108+
* @ingroup unitary
4109+
* @param[in,out] qureg object representing the set of all qubits
4110+
* @param[in] controlQubits list of the indices of qubits to control upon
4111+
* @param[in] numControls length of length `controlQubits`
4112+
* @param[in] targetQubits a list of the indices of the target qubits
4113+
* @param[in] targetPaulis a list of the Pauli operators around which to rotate the target qubits
4114+
* @param[in] numTargets length of list `targetQubits`
4115+
* @param[in] angle the angle by which the multi-qubit state is rotated around the Z axis
4116+
* @throws invalidQuESTInputError()
4117+
* - if any qubit in \p controlQubits and \p targetQubits is invalid, i.e. outside <b>[0, </b>`qureg.numQubitsRepresented`<b>)</b>
4118+
* - if \p controlQubits or \p targetQubits contain any repetitions
4119+
* - if any qubit in \p controlQubits is also in \p targetQubits (and vice versa)
4120+
* - if \p numTargets <b>< 1</b>
4121+
* - if \p numControls <b>< 1</b> (use multiRotateZ() for no controls)
4122+
* - if any element of \p targetPaulis is not one of `PAULI_I`, `PAULI_X`, `PAULI_Y`, `PAULI_Z`
4123+
* @throws segmentation-fault
4124+
* - if \p controlQubits contains fewer elements than \p numControls
4125+
* - if \p targetQubits contains fewer elements than \p numTargets
4126+
* - if \p targetPaulis contains fewer elements than \p numTargets
4127+
* @author Tyson Jones
4128+
*/
4129+
void multiControlledMultiRotatePauli(Qureg qureg, int* controlQubits, int numControls, int* targetQubits, enum pauliOpType* targetPaulis, int numTargets, qreal angle);
4130+
40304131
/** Computes the expected value of a product of Pauli operators.
40314132
* Letting \f$ \sigma = \otimes_j \hat{\sigma}_j \f$ be the operators indicated by \p pauliCodes
40324133
* and acting on qubits \p targetQubits, this function computes \f$ \langle \psi | \sigma | \psi \rangle \f$

QuEST/src/QuEST.c

+21
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,27 @@ void multiRotatePauli(Qureg qureg, int* targetQubits, enum pauliOpType* targetPa
703703
numTargets, angle);
704704
}
705705

706+
void multiControlledMultiRotatePauli(Qureg qureg, int* controlQubits, int numControls, int* targetQubits, enum pauliOpType* targetPaulis, int numTargets, qreal angle) {
707+
validateMultiControlsMultiTargets(qureg, controlQubits, numControls, targetQubits, numTargets, __func__);
708+
validatePauliCodes(targetPaulis, numTargets, __func__);
709+
710+
int conj=0;
711+
long long int ctrlMask = getQubitBitMask(controlQubits, numControls);
712+
statevec_multiControlledMultiRotatePauli(qureg, ctrlMask, targetQubits, targetPaulis, numTargets, angle, conj);
713+
if (qureg.isDensityMatrix) {
714+
conj = 1;
715+
int shift = qureg.numQubitsRepresented;
716+
shiftIndices(targetQubits, numTargets, shift);
717+
statevec_multiControlledMultiRotatePauli(qureg, ctrlMask<<shift, targetQubits, targetPaulis, numTargets, angle, conj);
718+
shiftIndices(targetQubits, numTargets, -shift);
719+
}
720+
721+
// @TODO: create actual QASM
722+
qasm_recordComment(qureg,
723+
"Here a %d-control %d-target multiControlledMultiRotatePauli of angle %g was performed (QASM not yet implemented)",
724+
numControls, numTargets, angle);
725+
}
726+
706727
void applyPhaseFunc(Qureg qureg, int* qubits, int numQubits, enum bitEncoding encoding, qreal* coeffs, qreal* exponents, int numTerms) {
707728
validateStateVecQureg(qureg, __func__);
708729
validateMultiQubits(qureg, qubits, numQubits, __func__);

QuEST/src/QuEST_common.c

+41
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,47 @@ void statevec_multiRotatePauli(
447447
}
448448
}
449449

450+
void statevec_multiControlledMultiRotatePauli(
451+
Qureg qureg, long long int ctrlMask, int* targetQubits, enum pauliOpType* targetPaulis, int numTargets, qreal angle,
452+
int applyConj
453+
) {
454+
qreal fac = 1/sqrt(2);
455+
qreal sgn = (applyConj)? 1 : -1;
456+
ComplexMatrix2 uRx = {.real={{fac,0},{0,fac}}, .imag={{0,sgn*fac},{sgn*fac,0}}}; // Rx(pi/2)* rotates Z -> Y
457+
ComplexMatrix2 uRy = {.real={{fac,fac},{-fac,fac}}, .imag={{0,0},{0,0}}}; // Ry(-pi/2) rotates Z -> X
458+
459+
// this function is controlled on the all-one state, so no ctrl flips
460+
long long int ctrlFlipMask = 0;
461+
462+
// mask may be modified to remove superfluous Identity ops
463+
long long int targMask = getQubitBitMask(targetQubits, numTargets);
464+
465+
// rotate basis so that exp(Z) will effect exp(Y) and exp(X)
466+
for (int t=0; t < numTargets; t++) {
467+
if (targetPaulis[t] == PAULI_I)
468+
targMask -= 1LL << targetQubits[t]; // remove target from mask
469+
if (targetPaulis[t] == PAULI_X)
470+
statevec_multiControlledUnitary(qureg, ctrlMask, ctrlFlipMask, targetQubits[t], uRy);
471+
if (targetPaulis[t] == PAULI_Y)
472+
statevec_multiControlledUnitary(qureg, ctrlMask, ctrlFlipMask, targetQubits[t], uRx);
473+
// (targetPaulis[t] == 3) is Z basis
474+
}
475+
476+
// does nothing if there are no qubits to 'rotate'
477+
if (targMask != 0)
478+
statevec_multiControlledMultiRotateZ(qureg, ctrlMask, targMask, (applyConj)? -angle : angle);
479+
480+
// undo X and Y basis rotations
481+
uRx.imag[0][1] *= -1; uRx.imag[1][0] *= -1;
482+
uRy.real[0][1] *= -1; uRy.real[1][0] *= -1;
483+
for (int t=0; t < numTargets; t++) {
484+
if (targetPaulis[t] == PAULI_X)
485+
statevec_multiControlledUnitary(qureg, ctrlMask, ctrlFlipMask, targetQubits[t], uRy);
486+
if (targetPaulis[t] == PAULI_Y)
487+
statevec_multiControlledUnitary(qureg, ctrlMask, ctrlFlipMask, targetQubits[t], uRx);
488+
}
489+
}
490+
450491
/* produces both pauli|qureg> or pauli * qureg (as a density matrix) */
451492
void statevec_applyPauliProd(Qureg workspace, int* targetQubits, enum pauliOpType* pauliCodes, int numTargets) {
452493

QuEST/src/QuEST_internal.h

+2
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ void statevec_multiControlledMultiRotateZ(Qureg qureg, long long int ctrlMask, l
253253

254254
void statevec_multiRotatePauli(Qureg qureg, int* targetQubits, enum pauliOpType* targetPaulis, int numTargets, qreal angle, int applyConj);
255255

256+
void statevec_multiControlledMultiRotatePauli(Qureg qureg, long long int ctrlMask, int* targetQubits, enum pauliOpType* targetPaulis, int numTargets, qreal angle, int applyConj);
257+
256258
void statevec_setWeightedQureg(Complex fac1, Qureg qureg1, Complex fac2, Qureg qureg2, Complex facOut, Qureg out);
257259

258260
void statevec_applyPauliSum(Qureg inQureg, enum pauliOpType* allCodes, qreal* termCoeffs, int numSumTerms, Qureg outQureg);

tests/test_unitaries.cpp

+163
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,169 @@ TEST_CASE( "multiControlledMultiQubitUnitary", "[unitaries]" ) {
11151115

11161116

11171117

1118+
/** @sa multiControlledMultiRotatePauli
1119+
* @ingroup unittest
1120+
* @author Tyson Jones
1121+
*/
1122+
TEST_CASE( "multiControlledMultiRotatePauli", "[unitaries]" ) {
1123+
1124+
PREPARE_TEST( quregVec, quregMatr, refVec, refMatr );
1125+
qreal param = getRandomReal(-4*M_PI, 4*M_PI);
1126+
1127+
SECTION( "correctness" ) {
1128+
1129+
// try all possible numbers of targets and controls
1130+
int numTargs = GENERATE_COPY( range(1,NUM_QUBITS) ); // leave space for min 1 control qubit
1131+
int maxNumCtrls = NUM_QUBITS - numTargs;
1132+
int numCtrls = GENERATE_COPY( range(1,maxNumCtrls+1) );
1133+
1134+
// generate all possible valid qubit arrangements
1135+
int* targs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), numTargs) );
1136+
int* ctrls = GENERATE_COPY( sublists(range(0,NUM_QUBITS), numCtrls, targs, numTargs) );
1137+
1138+
/* it's too expensive to try ALL Pauli sequences, via
1139+
* pauliOpType* paulis = GENERATE_COPY( pauliseqs(numTargs) );.
1140+
* Furthermore, take(10, pauliseqs(numTargs)) will try the same pauli codes.
1141+
* Hence, we instead opt to randomly generate pauliseqs
1142+
*/
1143+
pauliOpType paulis[numTargs];
1144+
for (int i=0; i<numTargs; i++)
1145+
paulis[i] = (pauliOpType) getRandomInt(0,4);
1146+
1147+
// exclude identities from reference matrix exp (they apply unwanted global phase)
1148+
int refTargs[numTargs];
1149+
int numRefTargs = 0;
1150+
1151+
QMatrix xMatr{{0,1},{1,0}};
1152+
QMatrix yMatr{{0,-qcomp(0,1)},{qcomp(0,1),0}};
1153+
QMatrix zMatr{{1,0},{0,-1}};
1154+
1155+
// build correct reference matrix by pauli-matrix exponentiation...
1156+
QMatrix pauliProd{{1}};
1157+
for (int i=0; i<numTargs; i++) {
1158+
QMatrix fac;
1159+
if (paulis[i] == PAULI_I) continue; // exclude I-targets from ref list
1160+
if (paulis[i] == PAULI_X) fac = xMatr;
1161+
if (paulis[i] == PAULI_Y) fac = yMatr;
1162+
if (paulis[i] == PAULI_Z) fac = zMatr;
1163+
pauliProd = getKroneckerProduct(fac, pauliProd);
1164+
1165+
// include this target in ref list
1166+
refTargs[numRefTargs++] = targs[i];
1167+
}
1168+
1169+
// produces exp(-i param/2 pauliProd), unless pauliProd = I
1170+
QMatrix op;
1171+
if (numRefTargs > 0)
1172+
op = getExponentialOfPauliMatrix(param, pauliProd);
1173+
1174+
SECTION( "state-vector" ) {
1175+
1176+
multiControlledMultiRotatePauli(quregVec, ctrls, numCtrls, targs, paulis, numTargs, param);
1177+
if (numRefTargs > 0)
1178+
applyReferenceOp(refVec, ctrls, numCtrls, refTargs, numRefTargs, op);
1179+
REQUIRE( areEqual(quregVec, refVec) );
1180+
}
1181+
SECTION( "density-matrix" ) {
1182+
1183+
multiControlledMultiRotatePauli(quregMatr, ctrls, numCtrls, targs, paulis, numTargs, param);
1184+
if (numRefTargs > 0)
1185+
applyReferenceOp(refMatr, ctrls, numCtrls, refTargs, numRefTargs, op);
1186+
REQUIRE( areEqual(quregMatr, refMatr, 10*REAL_EPS) );
1187+
}
1188+
}
1189+
SECTION( "input validation" ) {
1190+
1191+
// test all validation on both state-vector and density-matrix.
1192+
// want GENERATE_COPY( quregVec, quregMatr ), but too lazy to patch
1193+
// using github.com/catchorg/Catch2/issues/1809
1194+
Qureg regs[] = {quregVec, quregMatr};
1195+
Qureg qureg = regs[GENERATE(0,1)];
1196+
1197+
// over-sized array to prevent seg-fault in case of validation fail below
1198+
pauliOpType paulis[NUM_QUBITS+1];
1199+
for (int q=0; q<NUM_QUBITS+1; q++)
1200+
paulis[q] = PAULI_I;
1201+
1202+
SECTION( "pauli codes" ) {
1203+
1204+
int numCtrls = 1;
1205+
int ctrls[] = {3};
1206+
int numTargs = 3;
1207+
int targs[3] = {0, 1, 2};
1208+
1209+
// make a single Pauli invalid
1210+
paulis[GENERATE_COPY(range(0,numTargs))] = (pauliOpType) GENERATE( -1, 4 );
1211+
1212+
REQUIRE_THROWS_WITH( multiControlledMultiRotatePauli(qureg, ctrls, numCtrls, targs, paulis, numTargs, param), Contains("Invalid Pauli code"));
1213+
}
1214+
SECTION( "number of targets" ) {
1215+
1216+
// there cannot be more targets than qubits in register
1217+
// (numTargs=NUM_QUBITS is caught elsewhere, because that implies ctrls are invalid)
1218+
int numTargs = GENERATE( -1, 0, NUM_QUBITS+1 );
1219+
int numCtrls = 1;
1220+
int targs[NUM_QUBITS+1]; // prevents seg-fault if validation doesn't trigger
1221+
int ctrls[] = {0};
1222+
1223+
REQUIRE_THROWS_WITH( multiControlledMultiRotatePauli(qureg, ctrls, numCtrls, targs, paulis, numTargs, param), Contains("Invalid number of target") );
1224+
}
1225+
SECTION( "repetition in targets" ) {
1226+
1227+
int numCtrls = 1;
1228+
int numTargs = 3;
1229+
int ctrls[] = {0};
1230+
int targs[] = {1,2,2};
1231+
1232+
REQUIRE_THROWS_WITH( multiControlledMultiRotatePauli(qureg, ctrls, numCtrls, targs, paulis, numTargs, param), Contains("target") && Contains("unique"));
1233+
}
1234+
SECTION( "number of controls" ) {
1235+
1236+
int numCtrls = GENERATE( -1, 0, NUM_QUBITS, NUM_QUBITS+1 );
1237+
int numTargs = 1;
1238+
int ctrls[NUM_QUBITS+1]; // avoids seg-fault if validation not triggered
1239+
int targs[1] = {0};
1240+
1241+
REQUIRE_THROWS_WITH( multiControlledMultiRotatePauli(qureg, ctrls, numCtrls, targs, paulis, numTargs, param), Contains("Invalid number of control"));
1242+
}
1243+
SECTION( "repetition in controls" ) {
1244+
1245+
int numCtrls = 3;
1246+
int numTargs = 1;
1247+
int ctrls[] = {0,1,1};
1248+
int targs[] = {3};
1249+
1250+
REQUIRE_THROWS_WITH( multiControlledMultiRotatePauli(qureg, ctrls, numCtrls, targs, paulis, numTargs, param), Contains("control") && Contains("unique"));
1251+
}
1252+
SECTION( "control and target collision" ) {
1253+
1254+
int numCtrls = 3;
1255+
int numTargs = 3;
1256+
int ctrls[] = {0,1,2};
1257+
int targs[] = {3,1,4};
1258+
1259+
REQUIRE_THROWS_WITH( multiControlledMultiRotatePauli(qureg, ctrls, numCtrls, targs, paulis, numTargs, param), Contains("Control") && Contains("target") && Contains("disjoint"));
1260+
}
1261+
SECTION( "qubit indices" ) {
1262+
1263+
// valid inds
1264+
int numQb = 2;
1265+
int qb1[2] = {0,1};
1266+
int qb2[2] = {2,3};
1267+
1268+
// make qb1 invalid
1269+
int inv = GENERATE( -1, NUM_QUBITS );
1270+
qb1[GENERATE_COPY(range(0,numQb))] = inv;
1271+
1272+
REQUIRE_THROWS_WITH( multiControlledMultiRotatePauli(qureg, qb1, numQb, qb2, paulis, numQb, param), Contains("Invalid control") );
1273+
REQUIRE_THROWS_WITH( multiControlledMultiRotatePauli(qureg, qb2, numQb, qb1, paulis, numQb, param), Contains("Invalid target") );
1274+
}
1275+
}
1276+
CLEANUP_TEST( quregVec, quregMatr );
1277+
}
1278+
1279+
1280+
11181281
/** @sa multiControlledMultiRotateZ
11191282
* @ingroup unittest
11201283
* @author Tyson Jones

0 commit comments

Comments
 (0)