diff --git a/quest/include/multiplication.h b/quest/include/multiplication.h index 81d27a50..6422e69f 100644 --- a/quest/include/multiplication.h +++ b/quest/include/multiplication.h @@ -746,7 +746,6 @@ extern "C" { /// @notyetdoced -/// @notyetvalidated /// @see /// - leftapplyCompMatr1() /// - applyQubitProjector() @@ -754,7 +753,6 @@ void leftapplyQubitProjector(Qureg qureg, int qubit, int outcome); /// @notyetdoced -/// @notyetvalidated /// @see /// - leftapplyCompMatr1() /// - applyMultiQubitProjector() @@ -762,7 +760,6 @@ void leftapplyMultiQubitProjector(Qureg qureg, int* qubits, int* outcomes, int n /// @notyetdoced -/// @notyetvalidated /// @see /// - rightapplyCompMatr1() /// - applyQubitProjector() @@ -782,6 +779,25 @@ void rightapplyMultiQubitProjector(Qureg qureg, int* qubits, int* outcomes, int } #endif +#ifdef __cplusplus + + +/// @notyetdoced +/// @cppvectoroverload +/// @see leftapplyMultiQubitProjector() +void leftapplyMultiQubitProjector(Qureg qureg, std::vector qubits, std::vector outcomes); + + +/// @notyetdoced +/// @cppvectoroverload +/// @see rightapplyMultiQubitProjector() +void rightapplyMultiQubitProjector(Qureg qureg, std::vector qubits, std::vector outcomes); + + +#endif + +/** @} */ + /** diff --git a/quest/include/operations.h b/quest/include/operations.h index b138f200..bd74e1f8 100644 --- a/quest/include/operations.h +++ b/quest/include/operations.h @@ -2291,32 +2291,26 @@ extern "C" { /// @notyetdoced -/// @notyetvalidated int applyQubitMeasurement(Qureg qureg, int target); /// @notyetdoced -/// @notyetvalidated int applyQubitMeasurementAndGetProb(Qureg qureg, int target, qreal* probability); /// @notyetdoced -/// @notyetvalidated qreal applyForcedQubitMeasurement(Qureg qureg, int target, int outcome); /// @notyetdoced -/// @notyetvalidated qindex applyMultiQubitMeasurement(Qureg qureg, int* qubits, int numQubits); /// @notyetdoced -/// @notyetvalidated qindex applyMultiQubitMeasurementAndGetProb(Qureg qureg, int* qubits, int numQubits, qreal* probability); /// @notyetdoced -/// @notyetvalidated qreal applyForcedMultiQubitMeasurement(Qureg qureg, int* qubits, int* outcomes, int numQubits); @@ -2328,16 +2322,18 @@ qreal applyForcedMultiQubitMeasurement(Qureg qureg, int* qubits, int* outcomes, #ifdef __cplusplus -/// @notyettested -/// @notyetvalidated +/// @notyetdoced +/// @cppvectoroverload +/// @see applyMultiQubitMeasurement() +qindex applyMultiQubitMeasurement(Qureg qureg, std::vector qubits); + + /// @notyetdoced /// @cppvectoroverload /// @see applyMultiQubitMeasurementAndGetProb() qindex applyMultiQubitMeasurementAndGetProb(Qureg qureg, std::vector qubits, qreal* probability); -/// @notyettested -/// @notyetvalidated /// @notyetdoced /// @cppvectoroverload /// @see applyForcedMultiQubitMeasurement() @@ -2363,12 +2359,10 @@ extern "C" { /// @notyetdoced -/// @notyetvalidated void applyQubitProjector(Qureg qureg, int target, int outcome); /// @notyetdoced -/// @notyetvalidated void applyMultiQubitProjector(Qureg qureg, int* qubits, int* outcomes, int numQubits); @@ -2380,8 +2374,6 @@ void applyMultiQubitProjector(Qureg qureg, int* qubits, int* outcomes, int numQu #ifdef __cplusplus -/// @notyettested -/// @notyetvalidated /// @notyetdoced /// @cppvectoroverload /// @see applyMultiQubitProjector() @@ -2407,12 +2399,10 @@ extern "C" { /// @notyetdoced -/// @notyetvalidated void applyQuantumFourierTransform(Qureg qureg, int* targets, int numTargets); /// @notyetdoced -/// @notyetvalidated void applyFullQuantumFourierTransform(Qureg qureg); @@ -2424,8 +2414,6 @@ void applyFullQuantumFourierTransform(Qureg qureg); #ifdef __cplusplus -/// @notyettested -/// @notyetvalidated /// @notyetdoced /// @cppvectoroverload /// @see applyQuantumFourierTransform() diff --git a/quest/include/trotterisation.h b/quest/include/trotterisation.h index 7173c11b..9e7cad25 100644 --- a/quest/include/trotterisation.h +++ b/quest/include/trotterisation.h @@ -172,12 +172,17 @@ extern "C" { * - applyTrotterizedUnitaryTimeEvolution() * * @author Tyson Jones + * @author Vasco Ferreira (randomisation) */ void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); /// @notyetdoced /// @notyettested +/// +/// @author Tyson Jones +/// @author Vasco Ferreira (randomisation) +/// /// @see /// - applyTrotterizedPauliStrSumGadget() /// - applyControlledCompMatr1() @@ -186,6 +191,10 @@ void applyTrotterizedControlledPauliStrSumGadget(Qureg qureg, int control, Pauli /// @notyetdoced /// @notyettested +/// +/// @author Tyson Jones +/// @author Vasco Ferreira (randomisation) +/// /// @see /// - applyTrotterizedPauliStrSumGadget() /// - applyMultiControlledCompMatr1() @@ -194,6 +203,10 @@ void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls /// @notyetdoced /// @notyettested +/// +/// @author Tyson Jones +/// @author Vasco Ferreira (randomisation) +/// /// @see /// - applyTrotterizedPauliStrSumGadget() /// - applyMultiStateControlledCompMatr1() @@ -248,6 +261,7 @@ void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* con * - if @p reps is not a positive integer. * * @author Tyson Jones + * @author Vasco Ferreira (randomisation) */ void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qcomp angle, int order, int reps, bool permutePaulis); @@ -264,6 +278,10 @@ void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, q /// @notyetvalidated /// @notyetdoced /// @cppvectoroverload +/// +/// @author Tyson Jones +/// @author Vasco Ferreira (randomisation) +/// /// @see applyTrotterizedMultiControlledPauliStrSumGadget() void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, std::vector controls, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); @@ -272,6 +290,10 @@ void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, std::vector controls, std::vector states, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); @@ -294,9 +316,7 @@ extern "C" { #endif -/** @notyettested - * - * Unitarily time evolves @p qureg for the duration @p time under the time-independent Hamiltonian @p hamil, +/** Unitarily time evolves @p qureg for the duration @p time under the time-independent Hamiltonian @p hamil, * as approximated by symmetrized Trotterisation of the specified @p order and number of cycles @p reps. * * @formulae @@ -385,13 +405,12 @@ extern "C" { * - if @p reps is not a positive integer. * * @author Tyson Jones + * @author Vasco Ferreira (randomisation) */ void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal time, int order, int reps, bool permutePaulis); -/** @notyettested - * - * Simulates imaginary-time evolution of @p qureg for the duration @p tau under the time-independent +/** Simulates imaginary-time evolution of @p qureg for the duration @p tau under the time-independent * Hamiltonian @p hamil, as approximated by symmetrized Trotterisation of the specified @p order and * number of cycles @p reps. * @@ -517,6 +536,7 @@ void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal * - if @p reps is not a positive integer. * * @author Tyson Jones + * @author Vasco Ferreira (randomisation) */ void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal tau, int order, int reps, bool permutePaulis); @@ -665,6 +685,7 @@ void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qrea * - if @p reps is not a positive integer. * * @author Tyson Jones + * @author Vasco Ferreira (randomisation) */ void applyTrotterizedNoisyTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal* damps, PauliStrSum* jumps, int numJumps, qreal time, int order, int reps, bool permutePaulis); diff --git a/quest/src/api/operations.cpp b/quest/src/api/operations.cpp index e458605a..47a81e49 100644 --- a/quest/src/api/operations.cpp +++ b/quest/src/api/operations.cpp @@ -1717,6 +1717,11 @@ qreal applyForcedMultiQubitMeasurement(Qureg qureg, int* qubits, int* outcomes, } // end de-mangler +qindex applyMultiQubitMeasurement(Qureg qureg, vector qubits) { + + return applyMultiQubitMeasurement(qureg, qubits.data(), qubits.size()); +} + qindex applyMultiQubitMeasurementAndGetProb(Qureg qureg, vector qubits, qreal* probability) { return applyMultiQubitMeasurementAndGetProb(qureg, qubits.data(), qubits.size(), probability); diff --git a/quest/src/core/validation.cpp b/quest/src/core/validation.cpp index 2639b46f..3ac48505 100644 --- a/quest/src/core/validation.cpp +++ b/quest/src/core/validation.cpp @@ -3765,7 +3765,7 @@ void validate_measurementOutcomesMatchTargets(int numQubits, int numOutcomes, co {"${NUM_QUBITS}", numQubits}, {"${NUM_OUTCOMES}", numOutcomes}}; - assertThat(numQubits == numOutcomes, report::MEASUREMENT_OUTCOMES_MISMATCH_NUM_TARGETS, caller); + assertThat(numQubits == numOutcomes, report::MEASUREMENT_OUTCOMES_MISMATCH_NUM_TARGETS, vars, caller); } diff --git a/tests/unit/initialisations.cpp b/tests/unit/initialisations.cpp index 649b9290..ac1f1abd 100644 --- a/tests/unit/initialisations.cpp +++ b/tests/unit/initialisations.cpp @@ -486,8 +486,7 @@ TEST_CASE( "setQuregToWeightedSum", TEST_CATEGORY ) { SECTION( LABEL_VALIDATION ) { - // arbitrary existing qureg - Qureg qureg = getCachedStatevecs().begin()->second; + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "out qureg uninitialised" ) { @@ -702,7 +701,7 @@ TEST_CASE( "setQuregToMixture", TEST_CATEGORY ) { SECTION( "out qureg is statevector" ) { - Qureg badQureg = getCachedStatevecs().begin()->second; + Qureg badQureg = getArbitraryCachedStatevec(); REQUIRE_THROWS_WITH( setQuregToMixture(badQureg, nullptr, nullptr, 1), @@ -717,7 +716,7 @@ TEST_CASE( "setQuregToMixture", TEST_CATEGORY ) { // hide a statevector among them int badInd = GENERATE_COPY( range(0,numIn) ); - inQuregs[badInd] = getCachedStatevecs().begin()->second;; + inQuregs[badInd] = getArbitraryCachedStatevec(); REQUIRE_THROWS_WITH( setQuregToMixture(qureg, nullptr, inQuregs.data(), numIn), diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index adbffed3..a0a99086 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "tests/utils/config.hpp" @@ -1046,8 +1047,8 @@ void testOperationValidation(auto operation) { // (but beware statevecs vs density matrices permit // different num targets before validation is triggered) qureg = (Apply == rightapply)? - getCachedDensmatrs().begin()->second: - getCachedStatevecs().begin()->second; + getArbitraryCachedDensmatr(): + getArbitraryCachedStatevec(); // can only be validated when environment AND qureg // are distributed (over more than 1 node, of course) @@ -1137,9 +1138,8 @@ void testOperationValidation(auto operation) { if (Apply != rightapply) return; - // use any statevector - qureg = getCachedStatevecs().begin()->second; - + // override qureg in apiyFunc with a statevector + qureg = getArbitraryCachedStatevec(); REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("Expected a density matrix") ); } @@ -1411,7 +1411,55 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targs[] = {0, 1, 2}; + int numTargs = 3; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(badQureg, targs, numTargs), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargs[] = {0, 1, qureg.numQubits}; // latter is too large + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(qureg, badTargs, 3), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargs[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(qureg, dupTargs, 3), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE_COPY( -1, 0 ); + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(qureg, targs, badNumTargs), + ContainsSubstring("targets") || ContainsSubstring("target qubits") + ); + + badNumTargs = qureg.numQubits+1; + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(qureg, targs, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + } } @@ -1456,7 +1504,20 @@ TEST_CASE( "applyFullQuantumFourierTransform", TEST_CATEGORY_OPS ) { } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyFullQuantumFourierTransform(badQureg), + ContainsSubstring("invalid Qureg") + ); + } + } } @@ -1482,7 +1543,40 @@ TEST_CASE( "applyQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyQubitProjector(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + applyQubitProjector(qureg, badTarget, 0), + ContainsSubstring("target") + ); + } + + SECTION( "invalid outcome" ) { + + int badOutcome = GENERATE_COPY( -1, 2 ); + REQUIRE_THROWS_WITH( + applyQubitProjector(qureg, 0, badOutcome), + ContainsSubstring("outcome") + ); + } + + // projector does NOT validate outcome probability + } } @@ -1508,7 +1602,75 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + + SECTION( "invalid outcomes" ) { + + int badOutcomes[] = {0, 1, GENERATE( -1, 2 ) }; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("outcome") + ); + } + + SECTION( "targets mismatch outcomes (C++ only)" ) { + + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, {0,1,2}, {0,1}), + ContainsSubstring("outcomes") && ContainsSubstring("inconsistent with the given number of qubits") + ); + } + + // projector does NOT validate outcome probability + } } @@ -1547,7 +1709,77 @@ TEST_CASE( "applyForcedQubitMeasurement", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + + // below validation tests assume qubit 0 can collapse to either outcome + // (which does not require normalisation; qureg can be in the debug state) + initDebugState(qureg); + REQUIRE( calcProbOfQubitOutcome(qureg, 0, 0) > getValidationEpsilon() ); + REQUIRE( calcProbOfQubitOutcome(qureg, 0, 1) > getValidationEpsilon() ); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(qureg, badTarget, 0), + ContainsSubstring("target") + ); + } + + SECTION( "invalid outcome" ) { + + int badOutcome = GENERATE_COPY( -1, 2 ); + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(qureg, 0, badOutcome), + ContainsSubstring("outcome") + ); + } + + SECTION( "improbable outcome" ) { + + // precisely zero probability outcome + initZeroState(qureg); + int badOutcome = 1; // impossible + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(qureg, 0, badOutcome), + ContainsSubstring("impossibly unlikely") + ); + + // outcome of non-zero probability smaller than epsilon + qreal badTheta = 1E-8; + applyRotateX(qureg, 0, badTheta); // causes prob(1) = sin^2(theta) ~ 2.5E-17 + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(qureg, 0, badOutcome), + ContainsSubstring("impossibly unlikely") + ); + + // confirm that >epsilon probability is fine + initZeroState(qureg); + qreal goodTheta = 0.1; + applyRotateX(qureg, 0, goodTheta); + REQUIRE( + calcProbOfQubitOutcome(qureg, 0, badOutcome) > getValidationEpsilon() + ); + REQUIRE_NOTHROW( + applyForcedQubitMeasurement(qureg, 0, badOutcome) + ); + + // restore qureg state + initDebugState(qureg); + } + } } @@ -1593,7 +1825,111 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { setValidationEpsilonToDefault(); } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + // below validation tests assume the above parameters are valid (not impossibly unlikely) + initDebugState(qureg); + REQUIRE( calcProbOfMultiQubitOutcome(qureg, targets, outcomes, numTargets) > getValidationEpsilon() ); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits )}; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + + SECTION( "invalid outcomes" ) { + + int badOutcomes[] = {0, 1, GENERATE( -1, 2 )}; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("outcome") + ); + } + + SECTION( "improbable outcomes" ) { + + // impossible outcome + initZeroState(qureg); + int badOutcomes[] = {0, 0, 1}; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("impossibly unlikely") + ); + + // outcome of non-zero probability smaller than epsilon + qreal badTheta = 1E-8; + applyRotateX(qureg, targets[2], badTheta); + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("impossibly unlikely") + ); + + // confirm that >epsilon probability is fine + initZeroState(qureg); + qreal goodTheta = 0.1; + applyRotateX(qureg, targets[2], goodTheta); + int goodOutcomes[] = {0, 0, 1}; + REQUIRE( + calcProbOfMultiQubitOutcome(qureg, targets, goodOutcomes, numTargets) > getValidationEpsilon() + ); + REQUIRE_NOTHROW( + applyForcedMultiQubitMeasurement(qureg, targets, goodOutcomes, numTargets) + ); + + // restore qureg state + initDebugState(qureg); + } + + SECTION( "targets mismatch outcomes (C++ only)") { + + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, {0,1}, {0,1,1}), + ContainsSubstring("inconsistent") + ); + } + } } @@ -1630,7 +1966,55 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targets[] = {0, 1, 2}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(badQureg, targets, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY(-1, qureg.numQubits)}; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, badTargets, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, dupTargets, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, targets, badNumTargs), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, targets, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + } } @@ -1669,7 +2053,56 @@ TEST_CASE( "applyMultiQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targets[] = {0, 1, 2}; + int numTargets = 3; + qreal outProb = 0; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(badQureg, targets, numTargets, nullptr), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits )}; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, badTargets, numTargets, &outProb), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, dupTargets, numTargets, &outProb), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, targets, badNumTargs, &outProb), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, targets, badNumTargs, &outProb), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + } } @@ -1705,7 +2138,29 @@ TEST_CASE( "applyQubitMeasurement", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyQubitMeasurement(badQureg, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + applyQubitMeasurement(qureg, badTarget), + ContainsSubstring("target") + ); + } + } } @@ -1743,7 +2198,30 @@ TEST_CASE( "applyQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + qreal outProb = 0; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyQubitMeasurementAndGetProb(badQureg, 0, &outProb), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + applyQubitMeasurementAndGetProb(qureg, badTarget, &outProb), + ContainsSubstring("target") + ); + } + } } @@ -1859,7 +2337,23 @@ TEST_CASE( "applyNonUnitaryPauliGadget", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + PauliStr str = getPauliStr("XY", {0, 1}); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyNonUnitaryPauliGadget(badQureg, str, qcomp(0.5, 0)), + ContainsSubstring("invalid Qureg") + ); + } + + /// @todo remaining input validation + } } @@ -2097,7 +2591,7 @@ TEST_CASE( "rightapplyFullStateDiagMatrPower", TEST_CATEGORY_MULT LABEL_MIXED_DE } -TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); @@ -2119,11 +2613,44 @@ TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + leftapplyQubitProjector(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + leftapplyQubitProjector(qureg, badTarget, 0), + ContainsSubstring("target") + ); + } + + SECTION( "invalid outcome" ) { + + int badOutcome = GENERATE_COPY( -1, 2 ); + REQUIRE_THROWS_WITH( + leftapplyQubitProjector(qureg, 0, badOutcome), + ContainsSubstring("outcome") + ); + } + + // projector does NOT validate outcome probability + } } -TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); @@ -2144,11 +2671,53 @@ TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedDensmatr(); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "qureg is not density matrix" ) { + + Qureg badQureg = getArbitraryCachedStatevec(); + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(badQureg, 0, 0), + ContainsSubstring("received a statevector") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(qureg, badTarget, 0), + ContainsSubstring("target") + ); + } + + SECTION( "invalid outcome" ) { + + int badOutcome = GENERATE_COPY( -1, 2 ); + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(qureg, 0, badOutcome), + ContainsSubstring("outcome") + ); + } + + // projector does NOT validate outcome probability + } } -TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); @@ -2170,11 +2739,79 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + + SECTION( "invalid outcomes" ) { + + int badOutcomes[] = {0, 1, GENERATE( -1, 2 ) }; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("outcome") + ); + } + + SECTION( "targets mismatch outcomes (C++ only)" ) { + + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, {0,1,2}, {0,1}), + ContainsSubstring("inconsistent") + ); + } + + // projector does NOT validate outcome probability + } } -TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); @@ -2195,7 +2832,84 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedDensmatr(); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "qureg is not density matrix" ) { + + Qureg badQureg = getArbitraryCachedStatevec(); + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(badQureg, targets, outcomes, numTargets), + ContainsSubstring("received a statevector") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + + SECTION( "invalid outcomes" ) { + + int badOutcomes[] = {0, 1, GENERATE( -1, 2 ) }; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("outcome") + ); + } + + SECTION( "targets mismatch outcomes (C++ only)" ) { + + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, {0,1,2}, {0,1}), + ContainsSubstring("inconsistent") + ); + } + + // projector does NOT validate outcome probability + } } @@ -2225,7 +2939,27 @@ TEST_CASE( "leftapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + PauliStrSum sum = createRandomPauliStrSum(numQubits, 2); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + Qureg workspace = createCloneQureg(qureg); + REQUIRE_THROWS_WITH( + leftapplyPauliStrSum(badQureg, sum, workspace), + ContainsSubstring("invalid Qureg") + ); + destroyQureg(workspace); + } + + destroyPauliStrSum(sum); + + /// @todo remaining input validation + } } @@ -2254,7 +2988,27 @@ TEST_CASE( "rightapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + PauliStrSum sum = createRandomPauliStrSum(numQubits, 2); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + Qureg workspace = createCloneQureg(qureg); + REQUIRE_THROWS_WITH( + rightapplyPauliStrSum(badQureg, sum, workspace), + ContainsSubstring("invalid Qureg") + ); + destroyQureg(workspace); + } + + destroyPauliStrSum(sum); + + /// @todo remaining input validation + } } diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index dbf16786..35af81ba 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -2,6 +2,9 @@ * Unit tests of the trotterisation module. * * @author Tyson Jones + * @author Vasco Ferreira (initial Pauli permutation tests) + * @author Maurice Jamieson (real and imaginary time evolution tests) + * @author Oliver Thomson Brown (real and imaginary time evolution tests) * * @defgroup unittrotter Trotterisation * @ingroup unittests @@ -9,12 +12,22 @@ #include "quest.h" +#include #include +#include +#include +#include "tests/utils/macros.hpp" #include "tests/utils/cache.hpp" +#include "tests/utils/compare.hpp" #include "tests/utils/random.hpp" +#include +#include +using std::vector; +using std::string; +using namespace Catch::Matchers; /* * UTILITIES @@ -23,9 +36,7 @@ #define TEST_CATEGORY \ LABEL_UNIT_TAG "[trotterisation]" - void TEST_ON_CACHED_QUREGS(quregCache quregs, auto& refFunc, auto& regularFunc, auto& randFunc) { - for (auto& [label, refQureg]: quregs) { DYNAMIC_SECTION( label ) { @@ -49,12 +60,133 @@ void TEST_ON_CACHED_QUREGS(quregCache quregs, auto& refFunc, auto& regularFunc, } } +/* + * Prepare a Hamiltonian H under which dynamical + * evolution will be simulated via Trotterisation + * of unitary-time evolution operator e^(-itH). + * If the Hamiltonian was fixed/known in advance, + * we could instead use createInlinePauliStrSum() + * + * (Adapted from dynamics.cpp, @author Tyson Jones) + */ +PauliStrSum createHeisenbergHamiltonian(int numQubits) { + + // we prepare a Heisenberg XYZ spin-ring Hamiltonian, + // i.e. H = -1/2 sum( Jx XX + Jy YY + Jz ZZ + h Z ) + // upon all nearest neighbour qubits, with periodicity. + // The coefficients must be real for H to be Hermitian + // and ergo its time-evolution operator to be unitary, + // although they must be represented with a qcomp type. + vector operators = {"XX", "YY", "ZZ", "Z"}; + vector coefficients = {.1, .2, .3, .4}; // Jx,Jy,Jz,h + + // we will populate the below vectors with 4*numQubits + // elements which we could pre-allocate with .reserve, + // but we might incur Donald Knuth's justified wrath. + vector allStrings; + vector allCoeffs; + + // prepare all XX + YY + ZZ + for (int p=0; p<3; p++) { + for (int i=0; i targs = {i, (i+1)%numQubits}; + PauliStr str = getPauliStr(operators[p], targs); + + allStrings.push_back(str); + allCoeffs.push_back(coefficients[p]); + } + } + + // prepare Z + for (int i=0; i strings(numQubits); + vector coeffs(numQubits); + + for (int i=0; i}^{N} Z_{i}Z_{j} + * where, + * \mu = magField, + * J = interactionStrength, + * indicates nearest-neighbour interactions only, + * and boundary conditions are periodic such that site N-1 interacts with site 0. + * + * The asymmetricBias term can be used to break the symmetry of the system + * in order to 'choose' a preferred antiferromagnetic state, and ensure repeatable + * predictable outcomes. + * It adds a term of the form: + * -BZ_{0} + */ +PauliStrSum createIsingHamiltonian(int numQubits, qreal magField, + qreal interactionStrength, qreal asymmetricBias) { + const int NTERMS = 2 * numQubits + 1; + + vector coeffs; + vector pauli_terms; + coeffs.reserve(NTERMS); + pauli_terms.reserve(NTERMS); + + for (int i = 0; i < numQubits; ++i) { + pauli_terms.push_back(getPauliStr("Z", {i})); + coeffs.push_back(getQcomp(-magField, 0)); + + int next = (i + 1) % numQubits; + pauli_terms.push_back(getPauliStr("ZZ", {i, next})); + coeffs.push_back(getQcomp(-interactionStrength, 0)); + } + + pauli_terms.push_back(getPauliStr("Z", {0})); + coeffs.push_back(getQcomp(-asymmetricBias, 0)); + + return createPauliStrSum(pauli_terms, coeffs); +} + + +/* + * TESTS + */ + + /** * @todo * Basic validation for randomisation, should be expanded and merged * once the Trotterisation function tests have been implemented. */ - TEST_CASE( "randomisedTrotter", TEST_CATEGORY ) { SECTION( LABEL_CORRECTNESS ) { @@ -80,13 +212,439 @@ TEST_CASE( "randomisedTrotter", TEST_CATEGORY ) { destroyPauliStrSum(sum); } +} + +/* +* Time evolution tests +* @todo Add Pauli permutation variants +*/ +TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { + + // BEWARE: this test creates a new Qureg below which will have + // deployments chosen by the auto-deployer; it is ergo unpredictable + // whether it will be multithreaded, GPU-accelerated or distributed. + // This test is ergo checking only a single, unspecified deployment, + // unlike other tests which check all deployments. This is tolerable + // since (non-randomised) Trotterisation is merely invoking routines + // (Pauli gadgets) already independently tested across deployments + + SECTION( LABEL_CORRECTNESS ) { + + int numQubits = 20; + Qureg qureg = createQureg(numQubits); + initPlusState(qureg); + bool permutePaulis = false; + + PauliStrSum hamil = createHeisenbergHamiltonian(numQubits); + PauliStrSum observ = createAlternatingPauliObservable(numQubits); + + qreal dt = 0.1; + int order = 4; + int reps = 5; + int steps = 10; + + // nudge the epsilon used by internal validation functions up a bit + // as the time evolution operation plays badly with single precision + // Defaults for validation epsilon are: + // - 1E-5 at single precision + // - 1E-12 at double precision + // - 1E-13 at quad precision + qreal initialValidationEps = getValidationEpsilon(); + setValidationEpsilon(2 * initialValidationEps); + + /* + * Tolerance for floating-point comparisons + * Note that the underlying numerics are sensitive to the float + * precision AND to the number of threads. As such we set quite + * large epsilon values to account for the worst-case scenario which + * is single precision, single thread. The baseline for these results + * is double precision, multiple threads. + * + * Values (assuming default initialValidationEps) are: + * Single precision: + * obsEps = 0.03 + * normEps = 0.001 + * + * Double precision: + * obsEps = 3E-9 + * normEps = 1E-10 + * + * Quad precision: + * obsEps = 3E-10 + * normEps = 1E-11 + */ + qreal obsEps = 3E3 * initialValidationEps; + qreal normEps = 100 * initialValidationEps; + + vector refObservables = { + 19.26827777028073, + 20.34277275871839, + 21.21120737889526, + 21.86585902741717, + 22.30371711358924, + 22.52644660547882, + 22.54015748825067, + 22.35499202583118, + 21.9845541501027, + 21.44521638719462 + }; + + for (int i = 0; i < steps; i++) { + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt, order, reps, permutePaulis); + qreal expec = calcExpecPauliStrSum(qureg, observ); + + REQUIRE_THAT( expec, WithinAbs(refObservables[i], obsEps) ); + } + + // Verify state remains normalized + REQUIRE_THAT( calcTotalProb(qureg), WithinAbs(1.0, normEps) ); + + // Restore validation epsilon + setValidationEpsilon(initialValidationEps); + + destroyQureg(qureg); + destroyPauliStrSum(hamil); + destroyPauliStrSum(observ); + } + + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + PauliStrSum hamil = createHeisenbergHamiltonian(qureg.numQubits); + bool permutePaulis = false; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(badQureg, hamil, 0.1, 4, 5, permutePaulis), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "pauli sum uninitialized" ) { + + PauliStrSum badHamil = hamil; + badHamil.numTerms = 0; + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, badHamil, 0.1, 4, 5, permutePaulis), + ContainsSubstring("Pauli") + ); + } + + SECTION( "hamiltonian not hermitian" ) { + + vector strings; + vector coeffs; + strings.push_back(getPauliStr("X", {0})); + coeffs.push_back(getQcomp(1.0, 1.0)); + PauliStrSum nonHermitian = createPauliStrSum(strings, coeffs); + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, nonHermitian, 0.1, 4, 5, permutePaulis), + ContainsSubstring("Hermitian") + ); + destroyPauliStrSum(nonHermitian); + } + + SECTION( "pauli sum exceeds qureg qubits" ) { + + PauliStrSum largeHamil = createHeisenbergHamiltonian(qureg.numQubits + 1); + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, largeHamil, 0.1, 4, 5, permutePaulis), + ContainsSubstring("only compatible") + ); + destroyPauliStrSum(largeHamil); + } + + SECTION( "invalid trotter order (zero)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 0, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (negative)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, -2, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (odd, not 1)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 3, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter reps (zero)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 4, 0, permutePaulis), + ContainsSubstring("repetitions") + ); + } + + SECTION( "invalid trotter reps (negative)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 4, -3, permutePaulis), + ContainsSubstring("repetitions") + ); + } + + destroyPauliStrSum(hamil); + } +} + + +TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { + + // BEWARE: this test creates a new Qureg below which will have + // deployments chosen by the auto-deployer; it is ergo unpredictable + // whether it will be multithreaded, GPU-accelerated or distributed. + // This test is ergo checking only a single, unspecified deployment, + // unlike other tests which check all deployments. This is tolerable + // since (non-randomised) Trotterisation is merely invoking routines + // (Pauli gadgets) already independently tested across deployments + + SECTION( LABEL_CORRECTNESS ) { + + int numQubits = 16; + qreal tau = 0.1; + int order = 6; + int reps = 5; + int steps = 10; + bool permutePaulis = false; + + // Tolerance for ground state amplitude + qreal eps = 1E-2; + + // Ground state: all qubits align down (driven by strong magnetic field) + { + Qureg qureg = createQureg(numQubits); + initPlusState(qureg); + + PauliStrSum ising = createIsingHamiltonian(numQubits, 10.0, 0.0, 0.0); + + for (int i = 0; i < steps; ++i) { + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); + setQuregToRenormalized(qureg); + } + + qcomp amp = getQuregAmp(qureg, 0); + qreal amp_mag = amp.real() * amp.real() + amp.imag() * amp.imag(); + + REQUIRE_THAT( amp_mag, WithinAbs(1.0, eps) ); + + for (long long i = 1; i < (1LL << numQubits); i++) { + qcomp other_amp = getQuregAmp(qureg, i); + qreal other_mag = other_amp.real() * other_amp.real() + + other_amp.imag() * other_amp.imag(); + REQUIRE( other_mag < eps ); + } + + destroyQureg(qureg); + destroyPauliStrSum(ising); + } + + // Ground state: all qubits align up (driven by opposite magnetic field) + { + Qureg qureg = createQureg(numQubits); + initPlusState(qureg); + + PauliStrSum ising = createIsingHamiltonian(numQubits, -10.0, 0.0, 0.0); + + for (int i = 0; i < steps; ++i) { + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); + setQuregToRenormalized(qureg); + } + + long long last_state = (1LL << numQubits) - 1; + qcomp amp = getQuregAmp(qureg, last_state); + qreal amp_mag = amp.real() * amp.real() + amp.imag() * amp.imag(); + + REQUIRE_THAT( amp_mag, WithinAbs(1.0, eps) ); + + for (long long i = 0; i < (1LL << numQubits); i++) { + if (i == last_state) continue; + qcomp other_amp = getQuregAmp(qureg, i); + qreal other_mag = other_amp.real() * other_amp.real() + + other_amp.imag() * other_amp.imag(); + REQUIRE( other_mag < eps ); + } + + destroyQureg(qureg); + destroyPauliStrSum(ising); + } + + // Ground state: all qubits align down (driven by ferromagnetic interactions and bias) + { + Qureg qureg = createQureg(numQubits); + initPlusState(qureg); + + PauliStrSum ising = createIsingHamiltonian(numQubits, 0.0, 10.0, 10.0); + + for (int i = 0; i < steps; ++i) { + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); + setQuregToRenormalized(qureg); + } + + qcomp amp = getQuregAmp(qureg, 0); + qreal amp_mag = amp.real() * amp.real() + amp.imag() * amp.imag(); + + REQUIRE_THAT( amp_mag, WithinAbs(1.0, eps) ); + + for (long long i = 1; i < (1LL << numQubits); i++) { + qcomp other_amp = getQuregAmp(qureg, i); + qreal other_mag = other_amp.real() * other_amp.real() + + other_amp.imag() * other_amp.imag(); + REQUIRE( other_mag < eps ); + } + + destroyQureg(qureg); + destroyPauliStrSum(ising); + } + + // Ground state: alternating pattern (driven by antiferromagnetic interactions) + { + Qureg qureg = createQureg(numQubits); + initPlusState(qureg); + + PauliStrSum ising = createIsingHamiltonian(numQubits, 0.0, -10.0, 10.0); + + for (int i = 0; i < steps; ++i) { + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); + setQuregToRenormalized(qureg); + } + + unsigned long long idx = 0; + for (int i = 0; i < numQubits / 2; ++i) { + idx += (1ULL << (2*i + 1)); + } + + qcomp amp = getQuregAmp(qureg, idx); + qreal amp_mag = amp.real() * amp.real() + amp.imag() * amp.imag(); + + REQUIRE_THAT( amp_mag, WithinAbs(1.0, eps) ); + + for (long long i = 0; i < (1LL << numQubits); i++) { + if (i == idx) continue; + qcomp other_amp = getQuregAmp(qureg, i); + qreal other_mag = other_amp.real() * other_amp.real() + + other_amp.imag() * other_amp.imag(); + REQUIRE( other_mag < eps ); + } + + destroyQureg(qureg); + destroyPauliStrSum(ising); + } + } + + SECTION( LABEL_VALIDATION ) { + Qureg qureg = getArbitraryCachedStatevec(); + PauliStrSum ising = createIsingHamiltonian(qureg.numQubits, 1.0, 1.0, 0.0); + bool permutePaulis = false; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(badQureg, ising, 0.1, 4, 5, permutePaulis), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "pauli sum uninitialized" ) { + + PauliStrSum badIsing = ising; + badIsing.numTerms = 0; + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, badIsing, 0.1, 4, 5, permutePaulis), + ContainsSubstring("Pauli") + ); + } + + SECTION( "pauli sum exceeds qureg qubits" ) { + + PauliStrSum largeIsing = createIsingHamiltonian(qureg.numQubits+1, 1.0, 1.0, 0.0); + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, largeIsing, 0.1, 4, 5, permutePaulis), + ContainsSubstring("only compatible") + ); + destroyPauliStrSum(largeIsing); + } + + SECTION( "hamiltonian not hermitian" ) { + + vector strings; + vector coeffs; + strings.push_back(getPauliStr("X", {0})); + coeffs.push_back(getQcomp(1.0, 1.0)); + PauliStrSum nonHermitian = createPauliStrSum(strings, coeffs); + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, nonHermitian, 0.1, 4, 5, permutePaulis), + ContainsSubstring("Hermitian") + ); + destroyPauliStrSum(nonHermitian); + } + + SECTION( "invalid trotter order (zero)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 0, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (negative)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, -2, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (odd, not 1)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 3, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter reps (zero)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 4, 0, permutePaulis), + ContainsSubstring("repetitions") + ); + } + + SECTION( "invalid trotter reps (negative)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 4, -3, permutePaulis), + ContainsSubstring("repetitions") + ); + } + + destroyPauliStrSum(ising); + } } /** * @todo - * UNTESTED FUNCTIONS + * UNTESTED FUNCTIONS (NOT YET VALIDATED BY REFERENCE TESTS) */ void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qcomp angle, int order, int reps, bool permutePaulis); @@ -99,8 +657,4 @@ void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* controls, int* states, int numControls, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); -void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal time, int order, int reps, bool permutePaulis); - -void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal tau, int order, int reps, bool permutePaulis); - void applyTrotterizedNoisyTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal* damps, PauliStr* jumps, int numJumps, qreal time, int order, int reps, bool permutePaulis); diff --git a/tests/utils/cache.cpp b/tests/utils/cache.cpp index ec5001bc..0cb8a603 100644 --- a/tests/utils/cache.cpp +++ b/tests/utils/cache.cpp @@ -168,6 +168,22 @@ quregCache getAltCachedDensmatrs() { return densmatrs2; } +Qureg getArbitraryCachedStatevec() { + + // must not be called pre-creation nor post-destruction + DEMAND( !statevecs1.empty() ); + + return statevecs1.begin()->second; +} + +Qureg getArbitraryCachedDensmatr() { + + // must not be called pre-creation nor post-destruction + DEMAND( !densmatrs1.empty() ); + + return densmatrs1.begin()->second; +} + /* diff --git a/tests/utils/cache.hpp b/tests/utils/cache.hpp index ec8c71c7..87d8382f 100644 --- a/tests/utils/cache.hpp +++ b/tests/utils/cache.hpp @@ -40,6 +40,9 @@ quregCache getCachedDensmatrs(); quregCache getAltCachedStatevecs(); quregCache getAltCachedDensmatrs(); +Qureg getArbitraryCachedStatevec(); +Qureg getArbitraryCachedDensmatr(); + qvector getRefStatevec(); qmatrix getRefDensmatr();