Skip to content

Commit 851691d

Browse files
optimised one-qubit Paulis to use applyCompMatr1 (#682)
as per #638. The previous use of the Pauli-specific multi-qubit backend logic was suboptimal for single-target since it involved superfluous per-amplitude evaluation of bitstring parity. This introduced a performance regression in single-core QuEST v4 since v3 which used single-target matrix logic. This affects the performance of all explicitly single-target Pauli functions. Specifically: - applyPauliX() - applyControlledPauliX() - applyMultiControlledPauliX() - applyMultiStateControlledPauliX() - applyPauliY() - applyControlledPauliY() - applyMultiControlledPauliY() - applyMultiStateControlledPauliY() - applyMultiStateControlledPauliX() - applyPauliZ() - applyControlledPauliZ() - applyMultiControlledPauliZ() - applyMultiStateControlledPauliZ() - applyRotateX() - applyControlledRotateX() - applyMultiControlledRotateX() - applyMultiStateControlledRotateX() - applyRotateY() - applyControlledRotateY() - applyMultiControlledRotateY() - applyMultiStateControlledRotateY() - applyMultiStateControlledRotateX() - applyRotateZ() - applyControlledRotateZ() - applyMultiControlledRotateZ() - applyMultiStateControlledRotateZ() which are concisely summarised as X cX ccX csX Y cY ccY csY Z cZ ccZ csZ Rx cRx ccRx csRx Ry cRy ccRy csRy Rz cRz ccRz csRz. Beware this does not affect when incidentally passing a single-target through functions which can accept many, such as applyPauliStr() and applyPauliGadget(). Note too that further changes are expected necessary to recover single-core v3 performance.
1 parent ab8b560 commit 851691d

File tree

3 files changed

+98
-13
lines changed

3 files changed

+98
-13
lines changed

quest/src/api/operations.cpp

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -859,27 +859,38 @@ void applyMultiStateControlledPauliX(Qureg qureg, int* controls, int* states, in
859859
validate_controlsAndTarget(qureg, controls, numControls, target, __func__);
860860
validate_controlStates(states, numControls, __func__); // permits states==nullptr
861861

862-
// harmlessly re-validates
863-
applyMultiStateControlledPauliStr(qureg, controls, states, numControls, getPauliStr("X", {target}));
862+
// note that for the single-target scenario, we do not call the backend of
863+
// applyMultiStateControlledPauliStr() since it contains sub-optimal logic
864+
// which sees the factor of every amplitude dynamically evaluated (based on
865+
// index parity, etc); the dense-matrix element lookup is faster
866+
867+
/// @todo
868+
/// a bespoke all-pauli-X function (like in QuEST v3) will be faster still
869+
/// since it avoids all superfluous flops; check worthwhile for multi-qubit
870+
871+
// harmlessly re-validates, including hardcoded matrix unitarity
872+
CompMatr1 matrix = util_getPauliX();
873+
validateAndApplyAnyCtrlAnyTargUnitaryMatrix(qureg, controls, states, numControls, &target, 1, matrix, __func__);
864874
}
865875

866876
void applyMultiStateControlledPauliY(Qureg qureg, int* controls, int* states, int numControls, int target) {
867877
validate_quregFields(qureg, __func__);
868878
validate_controlsAndTarget(qureg, controls, numControls, target, __func__);
869879
validate_controlStates(states, numControls, __func__); // permits states==nullptr
870880

871-
// harmlessly re-validates
872-
applyMultiStateControlledPauliStr(qureg, controls, states, numControls, getPauliStr("Y", {target}));
881+
// harmlessly re-validates, including hardcoded matrix unitarity
882+
CompMatr1 matrix = util_getPauliY();
883+
validateAndApplyAnyCtrlAnyTargUnitaryMatrix(qureg, controls, states, numControls, &target, 1, matrix, __func__);
873884
}
874885

875886
void applyMultiStateControlledPauliZ(Qureg qureg, int* controls, int* states, int numControls, int target) {
876887
validate_quregFields(qureg, __func__);
877888
validate_controlsAndTarget(qureg, controls, numControls, target, __func__);
878889
validate_controlStates(states, numControls, __func__); // permits states==nullptr
879890

880-
// harmlessly re-validates
881-
DiagMatr1 matr = getDiagMatr1({1, -1});
882-
applyMultiStateControlledDiagMatr1(qureg, controls, states, numControls, target, matr);
891+
// harmlessly re-validates, including hardcoded matrix unitarity
892+
DiagMatr1 matrix = util_getPauliZ();
893+
validateAndApplyAnyCtrlAnyTargUnitaryMatrix(qureg, controls, states, numControls, &target, 1, matrix, __func__);
883894
}
884895

885896
} // end de-mangler
@@ -1077,26 +1088,44 @@ void applyMultiStateControlledRotateX(Qureg qureg, int* controls, int* states, i
10771088
validate_controlsAndTarget(qureg, controls, numControls, target, __func__);
10781089
validate_controlStates(states, numControls, __func__); // permits states==nullptr
10791090

1080-
// harmlessly re-validates
1081-
applyMultiStateControlledPauliGadget(qureg, controls, states, numControls, getPauliStr("X", {target}), angle);
1091+
// note that for the single-target scenario, we do not call the backend of
1092+
// applyMultiStateControlledPauliGadget() since it contains sub-optimal logic
1093+
// which sees the factor of every amplitude dynamically evaluated (based on
1094+
// index parity, etc); the dense-matrix element lookup is faster
1095+
1096+
// harmlessly re-validates, including hardcoded matrix unitarity
1097+
CompMatr1 matrix = util_getExpPauliX(angle);
1098+
validateAndApplyAnyCtrlAnyTargUnitaryMatrix(qureg, controls, states, numControls, &target, 1, matrix, __func__);
10821099
}
10831100

10841101
void applyMultiStateControlledRotateY(Qureg qureg, int* controls, int* states, int numControls, int target, qreal angle) {
10851102
validate_quregFields(qureg, __func__);
10861103
validate_controlsAndTarget(qureg, controls, numControls, target, __func__);
10871104
validate_controlStates(states, numControls, __func__); // permits states==nullptr
10881105

1089-
// harmlessly re-validates
1090-
applyMultiStateControlledPauliGadget(qureg, controls, states, numControls, getPauliStr("Y", {target}), angle);
1106+
// note that for the single-target scenario, we do not call the backend of
1107+
// applyMultiStateControlledPauliGadget() since it contains sub-optimal logic
1108+
// which sees the factor of every amplitude dynamically evaluated (based on
1109+
// index parity, etc); the dense-matrix element lookup is faster
1110+
1111+
// harmlessly re-validates, including hardcoded matrix unitarity
1112+
CompMatr1 matrix = util_getExpPauliY(angle);
1113+
validateAndApplyAnyCtrlAnyTargUnitaryMatrix(qureg, controls, states, numControls, &target, 1, matrix, __func__);
10911114
}
10921115

10931116
void applyMultiStateControlledRotateZ(Qureg qureg, int* controls, int* states, int numControls, int target, qreal angle) {
10941117
validate_quregFields(qureg, __func__);
10951118
validate_controlsAndTarget(qureg, controls, numControls, target, __func__);
10961119
validate_controlStates(states, numControls, __func__); // permits states==nullptr
10971120

1098-
// harmlessly re-validates
1099-
applyMultiStateControlledPauliGadget(qureg, controls, states, numControls, getPauliStr("Z", {target}), angle);
1121+
// note that for the single-target scenario, we do not call the backend of
1122+
// applyMultiStateControlledPauliGadget() since it contains sub-optimal logic
1123+
// which sees the factor of every amplitude dynamically evaluated (based on
1124+
// index parity, etc); the dense-matrix element lookup is faster
1125+
1126+
// harmlessly re-validates, including hardcoded matrix unitarity
1127+
DiagMatr1 matrix = util_getExpPauliZ(angle);
1128+
validateAndApplyAnyCtrlAnyTargUnitaryMatrix(qureg, controls, states, numControls, &target, 1, matrix, __func__);
11001129
}
11011130

11021131
} // end de-mangler

quest/src/core/utilities.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,54 @@ qcomp util_getPhaseFromGateAngle(qcomp angle) {
10071007
return - angle / 2;
10081008
}
10091009

1010+
CompMatr1 util_getPauliX() {
1011+
return getCompMatr1({
1012+
{0,1},
1013+
{1,0}
1014+
});
1015+
}
1016+
CompMatr1 util_getPauliY() {
1017+
return getCompMatr1({
1018+
{0,qcomp(0,-1)},
1019+
{qcomp(0,1),0}
1020+
});
1021+
}
1022+
DiagMatr1 util_getPauliZ() {
1023+
return getDiagMatr1({1,-1});
1024+
}
1025+
1026+
CompMatr1 util_getExpPauliX(qreal angle) {
1027+
1028+
qreal x = util_getPhaseFromGateAngle(angle);
1029+
qreal c = std::cos(x);
1030+
qreal s = std::sin(x);
1031+
1032+
return getCompMatr1({
1033+
{qcomp(c,0), qcomp(0,s)},
1034+
{qcomp(0,s), qcomp(c,0)}
1035+
});
1036+
}
1037+
1038+
CompMatr1 util_getExpPauliY(qreal angle) {
1039+
1040+
qreal x = util_getPhaseFromGateAngle(angle);
1041+
qreal c = std::cos(x);
1042+
qreal s = std::sin(x);
1043+
1044+
return getCompMatr1({
1045+
{ c, s},
1046+
{-s, c}
1047+
});
1048+
}
1049+
1050+
DiagMatr1 util_getExpPauliZ(qreal angle) {
1051+
1052+
qreal x = util_getPhaseFromGateAngle(angle);
1053+
qcomp y = qcomp(0, x);
1054+
1055+
return getDiagMatr1({std::exp(y), std::exp(-y)});
1056+
}
1057+
10101058

10111059

10121060
/*

quest/src/core/utilities.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,14 @@ std::pair<qindex, qindex> util_getBlockMultipleSubRange(qindex rangeLen, qindex
365365
qreal util_getPhaseFromGateAngle(qreal angle);
366366
qcomp util_getPhaseFromGateAngle(qcomp angle);
367367

368+
CompMatr1 util_getPauliX();
369+
CompMatr1 util_getPauliY();
370+
DiagMatr1 util_getPauliZ();
371+
372+
CompMatr1 util_getExpPauliX(qreal angle);
373+
CompMatr1 util_getExpPauliY(qreal angle);
374+
DiagMatr1 util_getExpPauliZ(qreal angle);
375+
368376

369377

370378
/*

0 commit comments

Comments
 (0)