diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index adbffed3..d2922421 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" @@ -1411,7 +1412,47 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + 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, 10}; + 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( "zero number of targets" ) { + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(qureg, targs, 0), + ContainsSubstring("targets") + ); + } + + destroyQureg(qureg); + } } @@ -1456,7 +1497,22 @@ TEST_CASE( "applyFullQuantumFourierTransform", TEST_CATEGORY_OPS ) { } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + + SECTION( "qureg uninitialised" ) { + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyFullQuantumFourierTransform(badQureg), + ContainsSubstring("invalid Qureg") + ); + } + + destroyQureg(qureg); + } } @@ -1482,7 +1538,30 @@ TEST_CASE( "applyQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyQubitProjector(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + // Try to project a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + REQUIRE_THROWS_WITH( + applyQubitProjector(qureg, 10, 0), + ContainsSubstring("target") + ); + } + + destroyQureg(qureg); + } } @@ -1508,7 +1587,51 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + // Try to project qubits that don't exist (qubit index 10 on a 5-qubit system) + int badTargets[] = {0, 1, 10}; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + // Try to project the same qubit twice in one operation + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "zero number of targets" ) { + // Try to project no qubits at all (empty list) + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, targets, outcomes, 0), + ContainsSubstring("targets") + ); + } + + destroyQureg(qureg); + } } @@ -1547,7 +1670,30 @@ TEST_CASE( "applyForcedQubitMeasurement", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + // Try to measure a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(qureg, 10, 0), + ContainsSubstring("target") + ); + } + + destroyQureg(qureg); + } } @@ -1593,7 +1739,51 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { setValidationEpsilonToDefault(); } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + // Try to measure qubits that don't exist (qubit index 10 on a 5-qubit system) + int badTargets[] = {0, 1, 10}; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + // Try to measure the same qubit twice in one operation + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "zero number of targets" ) { + // Try to measure no qubits at all (empty list) + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, targets, outcomes, 0), + ContainsSubstring("targets") + ); + } + + destroyQureg(qureg); + } } @@ -1630,7 +1820,50 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + int targets[] = {0, 1, 2}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(badQureg, targets, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + // Try to measure qubits that don't exist (qubit index 10 on a 5-qubit system) + int badTargets[] = {0, 1, 10}; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, badTargets, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + // Try to measure the same qubit twice in one operation + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, dupTargets, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "zero number of targets" ) { + // Try to measure no qubits at all (empty list) + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, targets, 0), + ContainsSubstring("targets") + ); + } + + destroyQureg(qureg); + } } @@ -1669,7 +1902,53 @@ TEST_CASE( "applyMultiQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + int targets[] = {0, 1, 2}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(badQureg, targets, numTargets, nullptr), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + // Try to measure qubits that don't exist (qubit index 10 on a 5-qubit system) + int badTargets[] = {0, 1, 10}; + qreal prob = 0; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, badTargets, numTargets, &prob), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + // Try to measure the same qubit twice in one operation + int dupTargets[] = {0, 1, 1}; + qreal prob = 0; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, dupTargets, numTargets, &prob), + ContainsSubstring("duplicate") + ); + } + + SECTION( "zero number of targets" ) { + // Try to measure no qubits at all (empty list) + qreal prob = 0; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, targets, 0, &prob), + ContainsSubstring("targets") + ); + } + + destroyQureg(qureg); + } } @@ -1705,7 +1984,30 @@ TEST_CASE( "applyQubitMeasurement", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyQubitMeasurement(badQureg, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + // Try to measure a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + REQUIRE_THROWS_WITH( + applyQubitMeasurement(qureg, 10), + ContainsSubstring("target") + ); + } + + destroyQureg(qureg); + } } @@ -1743,7 +2045,31 @@ TEST_CASE( "applyQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyQubitMeasurementAndGetProb(badQureg, 0, nullptr), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + // Try to measure a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + qreal prob = 0; + REQUIRE_THROWS_WITH( + applyQubitMeasurementAndGetProb(qureg, 10, &prob), + ContainsSubstring("target") + ); + } + + destroyQureg(qureg); + } } @@ -1774,8 +2100,6 @@ TEST_CASE( "applyFullStateDiagMatr", TEST_CATEGORY_OPS LABEL_MIXED_DEPLOY_TAG ) TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc); } } - - /// @todo input validation } @@ -1829,8 +2153,6 @@ TEST_CASE( "applyFullStateDiagMatrPower", TEST_CATEGORY_OPS LABEL_MIXED_DEPLOY_T setValidationEpsilonToDefault(); } - - /// @todo input validation } @@ -1859,7 +2181,23 @@ TEST_CASE( "applyNonUnitaryPauliGadget", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + PauliStr str = getPauliStr("XY", {0, 1}); + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyNonUnitaryPauliGadget(badQureg, str, qcomp(0.5, 0)), + ContainsSubstring("invalid Qureg") + ); + } + + destroyQureg(qureg); + } } @@ -1989,8 +2327,6 @@ TEST_CASE( "leftapplyFullStateDiagMatr", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_T TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc); } } - - /// @todo input validation } @@ -2014,8 +2350,6 @@ TEST_CASE( "rightapplyFullStateDiagMatr", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_ TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc); } } - - /// @todo input validation } @@ -2058,8 +2392,6 @@ TEST_CASE( "leftapplyFullStateDiagMatrPower", TEST_CATEGORY_MULT LABEL_MIXED_DEP TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc); } } - - /// @todo input validation } @@ -2093,7 +2425,23 @@ TEST_CASE( "rightapplyFullStateDiagMatrPower", TEST_CATEGORY_MULT LABEL_MIXED_DE } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + FullStateDiagMatr matr = getCachedFullStateDiagMatrs()[0]; + REQUIRE_THROWS_WITH( + rightapplyFullStateDiagMatrPower(badQureg, matr, qcomp(2.0, 0)), + ContainsSubstring("invalid Qureg") + ); + } + + destroyQureg(qureg); + } } @@ -2119,7 +2467,30 @@ TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + leftapplyQubitProjector(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + // Try to project a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + REQUIRE_THROWS_WITH( + leftapplyQubitProjector(qureg, 10, 0), + ContainsSubstring("target") + ); + } + + destroyQureg(qureg); + } } @@ -2144,7 +2515,30 @@ TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + // Try to project a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(qureg, 10, 0), + ContainsSubstring("target") + ); + } + + destroyQureg(qureg); + } } @@ -2170,7 +2564,51 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + // Try to project qubits that don't exist (qubit index 10 on a 5-qubit system) + int badTargets[] = {0, 1, 10}; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + // Try to project the same qubit twice in one operation + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "zero number of targets" ) { + // Try to project no qubits at all (empty list) + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, targets, outcomes, 0), + ContainsSubstring("targets") + ); + } + + destroyQureg(qureg); + } } @@ -2195,7 +2633,51 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + // Try to project qubits that don't exist (qubit index 10 on a 5-qubit system) + int badTargets[] = {0, 1, 10}; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + // Try to project the same qubit twice in one operation + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "zero number of targets" ) { + // Try to project no qubits at all (empty list) + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, targets, outcomes, 0), + ContainsSubstring("targets") + ); + } + + destroyQureg(qureg); + } } @@ -2225,7 +2707,26 @@ 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 ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + PauliStrSum sum = createRandomPauliStrSum(numQubits, 2); + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + Qureg workspace = createCloneQureg(qureg); + REQUIRE_THROWS_WITH( + leftapplyPauliStrSum(badQureg, sum, workspace), + ContainsSubstring("invalid Qureg") + ); + destroyQureg(workspace); + } + + destroyQureg(qureg); + destroyPauliStrSum(sum); + } } @@ -2254,7 +2755,26 @@ 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 ) { + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + PauliStrSum sum = createRandomPauliStrSum(numQubits, 2); + + SECTION( "qureg uninitialised" ) { + // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; + badQureg.numQubits = -1; + Qureg workspace = createCloneQureg(qureg); + REQUIRE_THROWS_WITH( + rightapplyPauliStrSum(badQureg, sum, workspace), + ContainsSubstring("invalid Qureg") + ); + destroyQureg(workspace); + } + + destroyQureg(qureg); + destroyPauliStrSum(sum); + } } diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index dbf16786..121b36b4 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,367 @@ TEST_CASE( "randomisedTrotter", TEST_CATEGORY ) { destroyPauliStrSum(sum); } +} + +/* +* Time evolution tests +* @todo Add Pauli permutation variants +*/ +TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { + + 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; + + // Tolerance for floating-point comparison + // Allows for minor numerical differences between runs + qreal eps = 1E-10; + + 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], eps) ); + } + + // Verify state remains normalized + REQUIRE_THAT( calcTotalProb(qureg), WithinAbs(1.0, 1E-10) ); + + destroyQureg(qureg); + destroyPauliStrSum(hamil); + destroyPauliStrSum(observ); + } + + SECTION( LABEL_VALIDATION ) { + + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + PauliStrSum hamil = createHeisenbergHamiltonian(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( "pauli sum exceeds qureg qubits" ) { + Qureg smallQureg = createQureg(3); + PauliStrSum largeHamil = createHeisenbergHamiltonian(numQubits); + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(smallQureg, largeHamil, 0.1, 4, 5, permutePaulis), + ContainsSubstring("only compatible") + ); + destroyQureg(smallQureg); + 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") + ); + } + + destroyQureg(qureg); + destroyPauliStrSum(hamil); + } +} + + +TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { + + 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 ) { + + int numQubits = 5; + Qureg qureg = createQureg(numQubits); + PauliStrSum ising = createIsingHamiltonian(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" ) { + Qureg smallQureg = createQureg(3); + PauliStrSum largeIsing = createIsingHamiltonian(numQubits, 1.0, 1.0, 0.0); + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(smallQureg, largeIsing, 0.1, 4, 5, permutePaulis), + ContainsSubstring("only compatible") + ); + destroyQureg(smallQureg); + 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") + ); + } + + destroyQureg(qureg); + 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 +585,6 @@ 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); + +void applyTrotterizedNoisyTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal* damps, PauliStr* jumps, int numJumps, qreal time, int order, int reps);