From 8519a2dc03fe7a30dd065d7324fbb1e26e8d8378 Mon Sep 17 00:00:00 2001 From: Maurice Jamieson Date: Wed, 14 Jan 2026 14:51:54 +0000 Subject: [PATCH 01/20] Added unit tests requested by Quantum Motion --- tests/unit/operations.cpp | 587 ++++++++++++++++++++++++++++++++-- tests/unit/trotterisation.cpp | 491 +++++++++++++++++++++++++++- 2 files changed, 1043 insertions(+), 35 deletions(-) diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index 05d46418..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" @@ -1042,8 +1043,12 @@ void testOperationValidation(auto operation) { SECTION( "targeted amps fit in node" ) { - // simplest to trigger validation using a statevector - qureg = getCachedStatevecs().begin()->second; + // use any qureg which is otherwise compatible + // (but beware statevecs vs density matrices permit + // different num targets before validation is triggered) + qureg = (Apply == rightapply)? + getCachedDensmatrs().begin()->second: + getCachedStatevecs().begin()->second; // can only be validated when environment AND qureg // are distributed (over more than 1 node, of course) @@ -1053,7 +1058,8 @@ void testOperationValidation(auto operation) { // can only be validated if forced ctrl qubits permit // enough remaining targets int minNumCtrls = (Ctrls == one)? 1 : 0; - int minNumTargs = numQubits - qureg.logNumNodes + 1; + int numVecQubits = (qureg.isDensityMatrix)? 2*numQubits : numQubits; + int minNumTargs = numVecQubits - qureg.logNumNodes + 1; int maxNumTargs = numQubits - minNumCtrls; if (minNumTargs > maxNumTargs) return; @@ -1406,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); + } } @@ -1451,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); + } } @@ -1477,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); + } } @@ -1503,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); + } } @@ -1542,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); + } } @@ -1588,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); + } } @@ -1625,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); + } } @@ -1664,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); + } } @@ -1700,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); + } } @@ -1738,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); + } } @@ -1769,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 } @@ -1824,8 +2153,6 @@ TEST_CASE( "applyFullStateDiagMatrPower", TEST_CATEGORY_OPS LABEL_MIXED_DEPLOY_T setValidationEpsilonToDefault(); } - - /// @todo input validation } @@ -1854,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); + } } @@ -1984,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 } @@ -2009,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 } @@ -2053,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 } @@ -2088,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); + } } @@ -2114,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); + } } @@ -2139,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); + } } @@ -2165,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); + } } @@ -2190,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); + } } @@ -2220,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); + } } @@ -2249,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 cc691402..f1ca904b 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -9,6 +9,22 @@ #include "quest.h" +#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; + /* @@ -19,10 +35,127 @@ LABEL_UNIT_TAG "[trotterisation]" +/* + * 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); +} + /** * @todo - * UNTESTED FUNCTIONS + * UNTESTED FUNCTIONS (NOT YET VALIDATED BY REFERENCE TESTS) */ void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qcomp angle, int order, int reps); @@ -35,8 +168,358 @@ void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* controls, int* states, int numControls, PauliStrSum sum, qreal angle, int order, int reps); -void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal time, int order, int reps); +void applyTrotterizedNoisyTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal* damps, PauliStr* jumps, int numJumps, qreal time, int order, int reps); -void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal tau, int order, int reps); -void applyTrotterizedNoisyTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal* damps, PauliStr* jumps, int numJumps, qreal time, int order, int reps); +/** @} */ + +/* + * TESTS + */ + +TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { + + SECTION( LABEL_CORRECTNESS ) { + + int numQubits = 20; + Qureg qureg = createQureg(numQubits); + initPlusState(qureg); + + 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); + 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); + + SECTION( "qureg uninitialised" ) { + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(badQureg, hamil, 0.1, 4, 5), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "pauli sum uninitialized" ) { + PauliStrSum badHamil = hamil; + badHamil.numTerms = 0; + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, badHamil, 0.1, 4, 5), + 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), + ContainsSubstring("only compatible") + ); + destroyQureg(smallQureg); + destroyPauliStrSum(largeHamil); + } + + SECTION( "invalid trotter order (zero)" ) { + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 0, 5), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (negative)" ) { + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, -2, 5), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (odd, not 1)" ) { + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 3, 5), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter reps (zero)" ) { + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 4, 0), + ContainsSubstring("repetitions") + ); + } + + SECTION( "invalid trotter reps (negative)" ) { + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 4, -3), + 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; + + // 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); + 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); + 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); + 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); + 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); + + SECTION( "qureg uninitialised" ) { + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(badQureg, ising, 0.1, 4, 5), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "pauli sum uninitialized" ) { + PauliStrSum badIsing = ising; + badIsing.numTerms = 0; + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, badIsing, 0.1, 4, 5), + 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), + 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), + ContainsSubstring("Hermitian") + ); + destroyPauliStrSum(nonHermitian); + } + + SECTION( "invalid trotter order (zero)" ) { + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 0, 5), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (negative)" ) { + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, -2, 5), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (odd, not 1)" ) { + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 3, 5), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter reps (zero)" ) { + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 4, 0), + ContainsSubstring("repetitions") + ); + } + + SECTION( "invalid trotter reps (negative)" ) { + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 4, -3), + ContainsSubstring("repetitions") + ); + } + + destroyQureg(qureg); + destroyPauliStrSum(ising); + } +} From 019ab1d176bda63dca68f0f963b3d767a534b8ea Mon Sep 17 00:00:00 2001 From: Oliver Thomson Brown Date: Thu, 15 Jan 2026 17:30:42 +0000 Subject: [PATCH 02/20] tests/unit/trotterisation.cpp: updated time evo calls to new API --- tests/unit/trotterisation.cpp | 58 +++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index 786b1937..41764ed7 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -178,13 +178,12 @@ PauliStrSum createIsingHamiltonian(int numQubits, qreal magField, * 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 ) { @@ -212,14 +211,18 @@ TEST_CASE( "randomisedTrotter", TEST_CATEGORY ) { } } - -TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { +/* +* 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); @@ -247,7 +250,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { }; for (int i = 0; i < steps; i++) { - applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt, order, reps); + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt, order, reps, permutePaulis); qreal expec = calcExpecPauliStrSum(qureg, observ); REQUIRE_THAT( expec, WithinAbs(refObservables[i], eps) ); @@ -266,12 +269,13 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { 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), + applyTrotterizedUnitaryTimeEvolution(badQureg, hamil, 0.1, 4, 5, permutePaulis), ContainsSubstring("invalid Qureg") ); } @@ -280,7 +284,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { PauliStrSum badHamil = hamil; badHamil.numTerms = 0; REQUIRE_THROWS_WITH( - applyTrotterizedUnitaryTimeEvolution(qureg, badHamil, 0.1, 4, 5), + applyTrotterizedUnitaryTimeEvolution(qureg, badHamil, 0.1, 4, 5, permutePaulis), ContainsSubstring("Pauli") ); } @@ -289,7 +293,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { Qureg smallQureg = createQureg(3); PauliStrSum largeHamil = createHeisenbergHamiltonian(numQubits); REQUIRE_THROWS_WITH( - applyTrotterizedUnitaryTimeEvolution(smallQureg, largeHamil, 0.1, 4, 5), + applyTrotterizedUnitaryTimeEvolution(smallQureg, largeHamil, 0.1, 4, 5, permutePaulis), ContainsSubstring("only compatible") ); destroyQureg(smallQureg); @@ -298,35 +302,35 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { SECTION( "invalid trotter order (zero)" ) { REQUIRE_THROWS_WITH( - applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 0, 5), + 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), + 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), + 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), + 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), + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 4, -3, permutePaulis), ContainsSubstring("repetitions") ); } @@ -346,6 +350,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { int order = 6; int reps = 5; int steps = 10; + bool permutePaulis = false; // Tolerance for ground state amplitude qreal eps = 1E-2; @@ -358,7 +363,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { PauliStrSum ising = createIsingHamiltonian(numQubits, 10.0, 0.0, 0.0); for (int i = 0; i < steps; ++i) { - applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps); + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); setQuregToRenormalized(qureg); } @@ -386,7 +391,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { PauliStrSum ising = createIsingHamiltonian(numQubits, -10.0, 0.0, 0.0); for (int i = 0; i < steps; ++i) { - applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps); + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); setQuregToRenormalized(qureg); } @@ -416,7 +421,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { PauliStrSum ising = createIsingHamiltonian(numQubits, 0.0, 10.0, 10.0); for (int i = 0; i < steps; ++i) { - applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps); + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); setQuregToRenormalized(qureg); } @@ -444,7 +449,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { PauliStrSum ising = createIsingHamiltonian(numQubits, 0.0, -10.0, 10.0); for (int i = 0; i < steps; ++i) { - applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps); + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); setQuregToRenormalized(qureg); } @@ -476,12 +481,13 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { 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), + applyTrotterizedImaginaryTimeEvolution(badQureg, ising, 0.1, 4, 5, permutePaulis), ContainsSubstring("invalid Qureg") ); } @@ -490,7 +496,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { PauliStrSum badIsing = ising; badIsing.numTerms = 0; REQUIRE_THROWS_WITH( - applyTrotterizedImaginaryTimeEvolution(qureg, badIsing, 0.1, 4, 5), + applyTrotterizedImaginaryTimeEvolution(qureg, badIsing, 0.1, 4, 5, permutePaulis), ContainsSubstring("Pauli") ); } @@ -499,7 +505,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { Qureg smallQureg = createQureg(3); PauliStrSum largeIsing = createIsingHamiltonian(numQubits, 1.0, 1.0, 0.0); REQUIRE_THROWS_WITH( - applyTrotterizedImaginaryTimeEvolution(smallQureg, largeIsing, 0.1, 4, 5), + applyTrotterizedImaginaryTimeEvolution(smallQureg, largeIsing, 0.1, 4, 5, permutePaulis), ContainsSubstring("only compatible") ); destroyQureg(smallQureg); @@ -514,7 +520,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { PauliStrSum nonHermitian = createPauliStrSum(strings, coeffs); REQUIRE_THROWS_WITH( - applyTrotterizedImaginaryTimeEvolution(qureg, nonHermitian, 0.1, 4, 5), + applyTrotterizedImaginaryTimeEvolution(qureg, nonHermitian, 0.1, 4, 5, permutePaulis), ContainsSubstring("Hermitian") ); destroyPauliStrSum(nonHermitian); @@ -522,35 +528,35 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { SECTION( "invalid trotter order (zero)" ) { REQUIRE_THROWS_WITH( - applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 0, 5), + 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), + 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), + 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), + 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), + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 4, -3, permutePaulis), ContainsSubstring("repetitions") ); } From 7356386faf4b0336adc8c49b22fecb81ec4e627d Mon Sep 17 00:00:00 2001 From: Oliver Thomson Brown Date: Thu, 15 Jan 2026 17:32:59 +0000 Subject: [PATCH 03/20] tests/unit/trotterisation.cpp: updated authorlist --- tests/unit/trotterisation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index 41764ed7..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 From 9e65b674824b11835d61646dc672186954fbfae4 Mon Sep 17 00:00:00 2001 From: Maurice Jamieson Date: Mon, 19 Jan 2026 09:47:06 +0000 Subject: [PATCH 04/20] Fixed valgrind errors --- tests/unit/operations.cpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index d2922421..8b7fc33f 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -2425,23 +2425,7 @@ TEST_CASE( "rightapplyFullStateDiagMatrPower", TEST_CATEGORY_MULT LABEL_MIXED_DE } } - 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); - } } @@ -2517,7 +2501,7 @@ TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_VALIDATION ) { int numQubits = 5; - Qureg qureg = createQureg(numQubits); + Qureg qureg = createDensityQureg(numQubits); SECTION( "qureg uninitialised" ) { // Invalidate the qureg by setting a negative qubit count @@ -2635,7 +2619,7 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_VALIDATION ) { int numQubits = 5; - Qureg qureg = createQureg(numQubits); + Qureg qureg = createDensityQureg(numQubits); int targets[] = {0, 1, 2}; int outcomes[] = {0, 1, 0}; int numTargets = 3; From 93eeb8c659aec22d159ddc5a925c4833d9cc0036 Mon Sep 17 00:00:00 2001 From: Oliver Thomson Brown <8394906+otbrown@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:33:11 +0000 Subject: [PATCH 05/20] tests/unit/trotterisation.cpp: tuned floating-point comparison epsilon to account for worst-case scenario which is single precision, single thread --- tests/unit/trotterisation.cpp | 43 ++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index 121b36b4..ca891f88 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -235,9 +235,37 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { int reps = 5; int steps = 10; - // Tolerance for floating-point comparison - // Allows for minor numerical differences between runs - qreal eps = 1E-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, @@ -256,12 +284,15 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt, order, reps, permutePaulis); qreal expec = calcExpecPauliStrSum(qureg, observ); - REQUIRE_THAT( expec, WithinAbs(refObservables[i], eps) ); + REQUIRE_THAT( expec, WithinAbs(refObservables[i], obsEps) ); } // Verify state remains normalized - REQUIRE_THAT( calcTotalProb(qureg), WithinAbs(1.0, 1E-10) ); - + REQUIRE_THAT( calcTotalProb(qureg), WithinAbs(1.0, normEps) ); + + // Restore validation epsilon + setValidationEpsilon(initialValidationEps); + destroyQureg(qureg); destroyPauliStrSum(hamil); destroyPauliStrSum(observ); From 9d661f6d5a7129b196355a6d46e1859b12880088 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 17:03:08 -0500 Subject: [PATCH 06/20] added get-arbitrary-qureg test util since it will be used frequently by new input validation --- tests/unit/initialisations.cpp | 7 +++---- tests/unit/operations.cpp | 9 ++++----- tests/utils/cache.cpp | 16 ++++++++++++++++ tests/utils/cache.hpp | 3 +++ 4 files changed, 26 insertions(+), 9 deletions(-) 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 8b7fc33f..7c1438af 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -1047,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) @@ -1138,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") ); } 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(); From 461e207e89959ae862ae7d3eb41835a8399236aa Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 17:21:36 -0500 Subject: [PATCH 07/20] removed Qureg creation in validation tests so that test failures do not cause memory leaks and e.g. add to valgrind noise. Tests now instead use getArbitraryCachedStatevec() or getArbitraryCachedDensmatr() to obtain an existing qureg with an arbitrary deployment. --- tests/unit/operations.cpp | 100 ++++++++++++---------------------- tests/unit/trotterisation.cpp | 26 ++++----- 2 files changed, 44 insertions(+), 82 deletions(-) diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index 7c1438af..71fd530b 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -1413,8 +1413,7 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + Qureg qureg = getArbitraryCachedStatevec(); int targs[] = {0, 1, 2}; int numTargs = 3; @@ -1428,7 +1427,8 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { } SECTION( "invalid target qubits" ) { - int badTargs[] = {0, 1, 10}; + + int badTargs[] = {0, 1, qureg.numQubits}; // latter is too large REQUIRE_THROWS_WITH( applyQuantumFourierTransform(qureg, badTargs, 3), ContainsSubstring("target") @@ -1450,7 +1450,6 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { ); } - destroyQureg(qureg); } } @@ -1498,8 +1497,7 @@ TEST_CASE( "applyFullQuantumFourierTransform", TEST_CATEGORY_OPS ) { SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { Qureg badQureg = qureg; @@ -1509,8 +1507,6 @@ TEST_CASE( "applyFullQuantumFourierTransform", TEST_CATEGORY_OPS ) { ContainsSubstring("invalid Qureg") ); } - - destroyQureg(qureg); } } @@ -1538,8 +1534,8 @@ TEST_CASE( "applyQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { // Invalidate the qureg by setting a negative qubit count @@ -1558,8 +1554,6 @@ TEST_CASE( "applyQubitProjector", TEST_CATEGORY_OPS ) { ContainsSubstring("target") ); } - - destroyQureg(qureg); } } @@ -1587,8 +1581,8 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); int targets[] = {0, 1, 2}; int outcomes[] = {0, 1, 0}; int numTargets = 3; @@ -1628,8 +1622,6 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { ContainsSubstring("targets") ); } - - destroyQureg(qureg); } } @@ -1670,8 +1662,8 @@ TEST_CASE( "applyForcedQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { // Invalidate the qureg by setting a negative qubit count @@ -1690,8 +1682,6 @@ TEST_CASE( "applyForcedQubitMeasurement", TEST_CATEGORY_OPS ) { ContainsSubstring("target") ); } - - destroyQureg(qureg); } } @@ -1739,8 +1729,8 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); int targets[] = {0, 1, 2}; int outcomes[] = {0, 1, 0}; int numTargets = 3; @@ -1780,8 +1770,6 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { ContainsSubstring("targets") ); } - - destroyQureg(qureg); } } @@ -1820,8 +1808,8 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); int targets[] = {0, 1, 2}; int numTargets = 3; @@ -1860,8 +1848,6 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { ContainsSubstring("targets") ); } - - destroyQureg(qureg); } } @@ -1902,8 +1888,8 @@ TEST_CASE( "applyMultiQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); int targets[] = {0, 1, 2}; int numTargets = 3; @@ -1945,8 +1931,6 @@ TEST_CASE( "applyMultiQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { ContainsSubstring("targets") ); } - - destroyQureg(qureg); } } @@ -1984,8 +1968,8 @@ TEST_CASE( "applyQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { // Invalidate the qureg by setting a negative qubit count @@ -2004,8 +1988,6 @@ TEST_CASE( "applyQubitMeasurement", TEST_CATEGORY_OPS ) { ContainsSubstring("target") ); } - - destroyQureg(qureg); } } @@ -2045,8 +2027,8 @@ TEST_CASE( "applyQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { // Invalidate the qureg by setting a negative qubit count @@ -2066,8 +2048,6 @@ TEST_CASE( "applyQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { ContainsSubstring("target") ); } - - destroyQureg(qureg); } } @@ -2181,8 +2161,8 @@ TEST_CASE( "applyNonUnitaryPauliGadget", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); PauliStr str = getPauliStr("XY", {0, 1}); SECTION( "qureg uninitialised" ) { @@ -2194,8 +2174,6 @@ TEST_CASE( "applyNonUnitaryPauliGadget", TEST_CATEGORY_OPS ) { ContainsSubstring("invalid Qureg") ); } - - destroyQureg(qureg); } } @@ -2451,8 +2429,8 @@ TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { // Invalidate the qureg by setting a negative qubit count @@ -2471,8 +2449,6 @@ TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_OPS ) { ContainsSubstring("target") ); } - - destroyQureg(qureg); } } @@ -2499,8 +2475,8 @@ TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createDensityQureg(numQubits); + + Qureg qureg = getArbitraryCachedDensmatr(); SECTION( "qureg uninitialised" ) { // Invalidate the qureg by setting a negative qubit count @@ -2519,8 +2495,6 @@ TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { ContainsSubstring("target") ); } - - destroyQureg(qureg); } } @@ -2548,8 +2522,8 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); int targets[] = {0, 1, 2}; int outcomes[] = {0, 1, 0}; int numTargets = 3; @@ -2589,8 +2563,6 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { ContainsSubstring("targets") ); } - - destroyQureg(qureg); } } @@ -2617,8 +2589,8 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createDensityQureg(numQubits); + + Qureg qureg = getArbitraryCachedDensmatr(); int targets[] = {0, 1, 2}; int outcomes[] = {0, 1, 0}; int numTargets = 3; @@ -2658,8 +2630,6 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { ContainsSubstring("targets") ); } - - destroyQureg(qureg); } } @@ -2691,8 +2661,8 @@ TEST_CASE( "leftapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); PauliStrSum sum = createRandomPauliStrSum(numQubits, 2); SECTION( "qureg uninitialised" ) { @@ -2707,7 +2677,6 @@ TEST_CASE( "leftapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) { destroyQureg(workspace); } - destroyQureg(qureg); destroyPauliStrSum(sum); } } @@ -2739,8 +2708,8 @@ TEST_CASE( "rightapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); PauliStrSum sum = createRandomPauliStrSum(numQubits, 2); SECTION( "qureg uninitialised" ) { @@ -2755,7 +2724,6 @@ TEST_CASE( "rightapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) destroyQureg(workspace); } - destroyQureg(qureg); destroyPauliStrSum(sum); } } diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index 121b36b4..1a1b765c 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -269,9 +269,8 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); - PauliStrSum hamil = createHeisenbergHamiltonian(numQubits); + Qureg qureg = getArbitraryCachedStatevec(); + PauliStrSum hamil = createHeisenbergHamiltonian(qureg.numQubits); bool permutePaulis = false; SECTION( "qureg uninitialised" ) { @@ -293,13 +292,12 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "pauli sum exceeds qureg qubits" ) { - Qureg smallQureg = createQureg(3); - PauliStrSum largeHamil = createHeisenbergHamiltonian(numQubits); + + PauliStrSum largeHamil = createHeisenbergHamiltonian(qureg.numQubits + 1); REQUIRE_THROWS_WITH( - applyTrotterizedUnitaryTimeEvolution(smallQureg, largeHamil, 0.1, 4, 5, permutePaulis), + applyTrotterizedUnitaryTimeEvolution(qureg, largeHamil, 0.1, 4, 5, permutePaulis), ContainsSubstring("only compatible") ); - destroyQureg(smallQureg); destroyPauliStrSum(largeHamil); } @@ -338,7 +336,6 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { ); } - destroyQureg(qureg); destroyPauliStrSum(hamil); } } @@ -481,9 +478,8 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); - PauliStrSum ising = createIsingHamiltonian(numQubits, 1.0, 1.0, 0.0); + Qureg qureg = getArbitraryCachedStatevec(); + PauliStrSum ising = createIsingHamiltonian(qureg.numQubits, 1.0, 1.0, 0.0); bool permutePaulis = false; SECTION( "qureg uninitialised" ) { @@ -505,13 +501,12 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "pauli sum exceeds qureg qubits" ) { - Qureg smallQureg = createQureg(3); - PauliStrSum largeIsing = createIsingHamiltonian(numQubits, 1.0, 1.0, 0.0); + + PauliStrSum largeIsing = createIsingHamiltonian(qureg.numQubits+1, 1.0, 1.0, 0.0); REQUIRE_THROWS_WITH( - applyTrotterizedImaginaryTimeEvolution(smallQureg, largeIsing, 0.1, 4, 5, permutePaulis), + applyTrotterizedImaginaryTimeEvolution(qureg, largeIsing, 0.1, 4, 5, permutePaulis), ContainsSubstring("only compatible") ); - destroyQureg(smallQureg); destroyPauliStrSum(largeIsing); } @@ -564,7 +559,6 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { ); } - destroyQureg(qureg); destroyPauliStrSum(ising); } } From d024b862aa77c8088849a99818c4b56b86873a6a Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 18:38:42 -0500 Subject: [PATCH 08/20] restoring missing-validation comments since the validation for these functions wasn't added. Such functions have additional tests to their tested counterparts; for example, validating that matrix elements are non-zero when given a negative exponent --- tests/unit/operations.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index 71fd530b..eff02c9d 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -2079,6 +2079,8 @@ 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 } @@ -2132,6 +2134,8 @@ TEST_CASE( "applyFullStateDiagMatrPower", TEST_CATEGORY_OPS LABEL_MIXED_DEPLOY_T setValidationEpsilonToDefault(); } + + /// @todo input validation } @@ -2304,6 +2308,8 @@ 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 } @@ -2327,6 +2333,8 @@ TEST_CASE( "rightapplyFullStateDiagMatr", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_ TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc); } } + + /// @todo input validation } @@ -2369,6 +2377,8 @@ TEST_CASE( "leftapplyFullStateDiagMatrPower", TEST_CATEGORY_MULT LABEL_MIXED_DEP TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc); } } + + /// @todo input validation } @@ -2402,7 +2412,7 @@ TEST_CASE( "rightapplyFullStateDiagMatrPower", TEST_CATEGORY_MULT LABEL_MIXED_DE } } - + /// @todo input validation } From 27f614ef328cd123428ac63ddad701c8939173b6 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 18:51:05 -0500 Subject: [PATCH 09/20] fixing test category --- tests/unit/operations.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index eff02c9d..72aa4858 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -2416,7 +2416,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 ); @@ -2463,7 +2463,7 @@ TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_OPS ) { } -TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); @@ -2509,7 +2509,7 @@ TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { } -TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); @@ -2577,7 +2577,7 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { } -TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); From 6f5644fc09adbe3a6440dfa4f7db6f6ef36dfc68 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 18:53:55 -0500 Subject: [PATCH 10/20] added missing operation validation tests --- tests/unit/operations.cpp | 389 ++++++++++++++++++++++++++++++-------- 1 file changed, 315 insertions(+), 74 deletions(-) diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index 72aa4858..802b0a23 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -1418,6 +1418,7 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { int numTargs = 3; SECTION( "qureg uninitialised" ) { + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1436,6 +1437,7 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { } SECTION( "duplicate target qubits" ) { + int dupTargs[] = {0, 1, 1}; REQUIRE_THROWS_WITH( applyQuantumFourierTransform(qureg, dupTargs, 3), @@ -1443,13 +1445,20 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { ); } - SECTION( "zero number of targets" ) { + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE_COPY( -1, 0 ); REQUIRE_THROWS_WITH( - applyQuantumFourierTransform(qureg, targs, 0), - ContainsSubstring("targets") + 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") + ); + } } } @@ -1500,6 +1509,7 @@ TEST_CASE( "applyFullQuantumFourierTransform", TEST_CATEGORY_OPS ) { Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1538,7 +1548,7 @@ TEST_CASE( "applyQubitProjector", TEST_CATEGORY_OPS ) { Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1548,12 +1558,24 @@ TEST_CASE( "applyQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( "invalid target qubit" ) { - // Try to project a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - applyQubitProjector(qureg, 10, 0), + 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 } } @@ -1588,7 +1610,7 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { int numTargets = 3; SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1598,8 +1620,8 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { } 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}; + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; REQUIRE_THROWS_WITH( applyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), ContainsSubstring("target") @@ -1607,7 +1629,7 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { } 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), @@ -1615,13 +1637,31 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { ); } - SECTION( "zero number of targets" ) { - // Try to project no qubits at all (empty list) + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); REQUIRE_THROWS_WITH( - applyMultiQubitProjector(qureg, targets, outcomes, 0), + 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") + ); } + + // projector does NOT validate outcome probability } } @@ -1665,8 +1705,14 @@ TEST_CASE( "applyForcedQubitMeasurement", TEST_CATEGORY_OPS ) { 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" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1676,12 +1722,55 @@ TEST_CASE( "applyForcedQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( "invalid target qubit" ) { - // Try to measure a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - applyForcedQubitMeasurement(qureg, 10, 0), + 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); + } } } @@ -1735,8 +1824,12 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { 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" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1746,8 +1839,8 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } 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}; + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits )}; REQUIRE_THROWS_WITH( applyForcedMultiQubitMeasurement(qureg, badTargets, outcomes, numTargets), ContainsSubstring("target") @@ -1755,7 +1848,7 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } 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), @@ -1763,12 +1856,62 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { ); } - SECTION( "zero number of targets" ) { - // Try to measure no qubits at all (empty list) + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); REQUIRE_THROWS_WITH( - applyForcedMultiQubitMeasurement(qureg, targets, outcomes, 0), + 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); } } } @@ -1814,7 +1957,7 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { int numTargets = 3; SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1824,8 +1967,8 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } 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}; + + int badTargets[] = {0, 1, GENERATE_COPY(-1, qureg.numQubits)}; REQUIRE_THROWS_WITH( applyMultiQubitMeasurement(qureg, badTargets, numTargets), ContainsSubstring("target") @@ -1833,7 +1976,7 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } 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), @@ -1841,12 +1984,19 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { ); } - SECTION( "zero number of targets" ) { - // Try to measure no qubits at all (empty list) + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); REQUIRE_THROWS_WITH( - applyMultiQubitMeasurement(qureg, targets, 0), + 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") + ); } } } @@ -1892,9 +2042,10 @@ TEST_CASE( "applyMultiQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { Qureg qureg = getArbitraryCachedStatevec(); int targets[] = {0, 1, 2}; int numTargets = 3; + qreal outProb = 0; SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1904,32 +2055,36 @@ TEST_CASE( "applyMultiQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { } 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; + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits )}; REQUIRE_THROWS_WITH( - applyMultiQubitMeasurementAndGetProb(qureg, badTargets, numTargets, &prob), + applyMultiQubitMeasurementAndGetProb(qureg, badTargets, numTargets, &outProb), 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), + applyMultiQubitMeasurementAndGetProb(qureg, dupTargets, numTargets, &outProb), ContainsSubstring("duplicate") ); } - SECTION( "zero number of targets" ) { - // Try to measure no qubits at all (empty list) - qreal prob = 0; + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); REQUIRE_THROWS_WITH( - applyMultiQubitMeasurementAndGetProb(qureg, targets, 0, &prob), + 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") + ); } } } @@ -1972,7 +2127,7 @@ TEST_CASE( "applyQubitMeasurement", TEST_CATEGORY_OPS ) { Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1982,9 +2137,10 @@ TEST_CASE( "applyQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( "invalid target qubit" ) { - // Try to measure a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - applyQubitMeasurement(qureg, 10), + applyQubitMeasurement(qureg, badTarget), ContainsSubstring("target") ); } @@ -2029,22 +2185,23 @@ TEST_CASE( "applyQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { SECTION( LABEL_VALIDATION ) { Qureg qureg = getArbitraryCachedStatevec(); + qreal outProb = 0; 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), + applyQubitMeasurementAndGetProb(badQureg, 0, &outProb), 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; + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - applyQubitMeasurementAndGetProb(qureg, 10, &prob), + applyQubitMeasurementAndGetProb(qureg, badTarget, &outProb), ContainsSubstring("target") ); } @@ -2170,7 +2327,7 @@ TEST_CASE( "applyNonUnitaryPauliGadget", TEST_CATEGORY_OPS ) { 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( @@ -2178,6 +2335,8 @@ TEST_CASE( "applyNonUnitaryPauliGadget", TEST_CATEGORY_OPS ) { ContainsSubstring("invalid Qureg") ); } + + /// @todo remaining input validation } } @@ -2443,7 +2602,7 @@ TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_MULT ) { Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -2453,12 +2612,24 @@ TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_MULT ) { } SECTION( "invalid target qubit" ) { - // Try to project a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - leftapplyQubitProjector(qureg, 10, 0), + 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 } } @@ -2489,7 +2660,7 @@ TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_MULT ) { Qureg qureg = getArbitraryCachedDensmatr(); SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -2498,13 +2669,34 @@ TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_MULT ) { ); } + SECTION( "qureg is not density matrix" ) { + + Qureg badQureg = getArbitraryCachedStatevec(); + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(badQureg, 0, 0), + ContainsSubstring("received a statevector") + ); + } + SECTION( "invalid target qubit" ) { - // Try to project a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - rightapplyQubitProjector(qureg, 10, 0), + 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 } } @@ -2539,7 +2731,7 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { int numTargets = 3; SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -2549,8 +2741,8 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { } 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}; + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; REQUIRE_THROWS_WITH( leftapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), ContainsSubstring("target") @@ -2558,7 +2750,7 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { } 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), @@ -2566,13 +2758,31 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { ); } - SECTION( "zero number of targets" ) { - // Try to project no qubits at all (empty list) + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); REQUIRE_THROWS_WITH( - leftapplyMultiQubitProjector(qureg, targets, outcomes, 0), + 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") + ); + } + + // projector does NOT validate outcome probability } } @@ -2606,7 +2816,7 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { int numTargets = 3; SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -2615,9 +2825,18 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { ); } + 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" ) { - // Try to project qubits that don't exist (qubit index 10 on a 5-qubit system) - int badTargets[] = {0, 1, 10}; + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; REQUIRE_THROWS_WITH( rightapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), ContainsSubstring("target") @@ -2625,7 +2844,7 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { } 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), @@ -2633,13 +2852,31 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { ); } - SECTION( "zero number of targets" ) { - // Try to project no qubits at all (empty list) + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); REQUIRE_THROWS_WITH( - rightapplyMultiQubitProjector(qureg, targets, outcomes, 0), + 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") + ); + } + + // projector does NOT validate outcome probability } } @@ -2676,7 +2913,7 @@ TEST_CASE( "leftapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) { 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); @@ -2688,6 +2925,8 @@ TEST_CASE( "leftapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) { } destroyPauliStrSum(sum); + + /// @todo remaining input validation } } @@ -2723,7 +2962,7 @@ TEST_CASE( "rightapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) 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); @@ -2735,6 +2974,8 @@ TEST_CASE( "rightapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) } destroyPauliStrSum(sum); + + /// @todo remaining input validation } } From 3cd1b4fda9919aaa12738f68d726eaa1af50e726 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 18:54:30 -0500 Subject: [PATCH 11/20] fixed indentation --- tests/unit/trotterisation.cpp | 84 +++++++++++++++++------------------ 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index 1a1b765c..362c0091 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -222,49 +222,49 @@ 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) ); + 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); - destroyQureg(qureg); - destroyPauliStrSum(hamil); - destroyPauliStrSum(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 ) { From 708811d85e2d452d9ac70bcb2c672ca42c3b2ba2 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 19:42:54 -0500 Subject: [PATCH 12/20] making spacing consistent and adding a missing Hermiticity validation to applyTrotterizedUnitaryTimeEvolution test --- tests/unit/trotterisation.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index 362c0091..9cb4599f 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -274,6 +274,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { bool permutePaulis = false; SECTION( "qureg uninitialised" ) { + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -283,6 +284,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "pauli sum uninitialized" ) { + PauliStrSum badHamil = hamil; badHamil.numTerms = 0; REQUIRE_THROWS_WITH( @@ -291,6 +293,21 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { ); } + 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); @@ -302,6 +319,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter order (zero)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 0, 5, permutePaulis), ContainsSubstring("order") @@ -309,6 +327,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter order (negative)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, -2, 5, permutePaulis), ContainsSubstring("order") @@ -316,6 +335,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter order (odd, not 1)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 3, 5, permutePaulis), ContainsSubstring("order") @@ -323,6 +343,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter reps (zero)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 4, 0, permutePaulis), ContainsSubstring("repetitions") @@ -330,6 +351,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter reps (negative)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 4, -3, permutePaulis), ContainsSubstring("repetitions") @@ -483,6 +505,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { bool permutePaulis = false; SECTION( "qureg uninitialised" ) { + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -492,6 +515,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "pauli sum uninitialized" ) { + PauliStrSum badIsing = ising; badIsing.numTerms = 0; REQUIRE_THROWS_WITH( @@ -511,6 +535,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "hamiltonian not hermitian" ) { + vector strings; vector coeffs; strings.push_back(getPauliStr("X", {0})); @@ -525,6 +550,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter order (zero)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 0, 5, permutePaulis), ContainsSubstring("order") @@ -532,6 +558,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter order (negative)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, -2, 5, permutePaulis), ContainsSubstring("order") @@ -539,6 +566,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter order (odd, not 1)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 3, 5, permutePaulis), ContainsSubstring("order") @@ -546,6 +574,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter reps (zero)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 4, 0, permutePaulis), ContainsSubstring("repetitions") @@ -553,6 +582,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter reps (negative)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 4, -3, permutePaulis), ContainsSubstring("repetitions") From ef8cc79455d6a210630629da36b8c89c91cc157d Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 19:50:30 -0500 Subject: [PATCH 13/20] added warning about untested deployments --- tests/unit/trotterisation.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index 9cb4599f..289057ed 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -220,6 +220,14 @@ TEST_CASE( "randomisedTrotter", TEST_CATEGORY ) { */ 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; @@ -365,6 +373,14 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { 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; From abb083caff94f9782214a738e4c71e998eaedd5e Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 19:53:11 -0500 Subject: [PATCH 14/20] removed defunct signature --- tests/unit/trotterisation.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index 289057ed..cfa5366f 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -626,5 +626,3 @@ 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 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); From 97941919e6f393744dc8c34922b9a99b362468b3 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 20:23:32 -0500 Subject: [PATCH 15/20] patching C++ validation err msg Previously, an error message of the C++ API was not substituting in values for its placeholder variables. This affected the C++ variants of the below functions when passing vectors for the targets and outcome parameters of mismatching length: - calcProbOfMultiQubitOutcome - leftapplyMultiQubitProjector - rightapplyMultiQubitProjector - applyMultiQubitProjector - applyForcedMultiQubitMeasurement --- quest/src/core/validation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } From 6d39d122474e578bcd2f93889be070667ce1d590 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 20:24:48 -0500 Subject: [PATCH 16/20] added missing C++ API signatures --- quest/include/multiplication.h | 19 +++++++++++++++++++ quest/include/operations.h | 8 ++++++-- quest/src/api/operations.cpp | 5 +++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/quest/include/multiplication.h b/quest/include/multiplication.h index 81d27a50..b79a0555 100644 --- a/quest/include/multiplication.h +++ b/quest/include/multiplication.h @@ -782,6 +782,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..1b0e2240 100644 --- a/quest/include/operations.h +++ b/quest/include/operations.h @@ -2328,8 +2328,12 @@ 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() 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); From a0d306114594478941b6fb7f63711a3314a18bb3 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 20:25:15 -0500 Subject: [PATCH 17/20] added C++-API validation tests --- tests/unit/operations.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index 802b0a23..a0a99086 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -1661,6 +1661,14 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { ); } + 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 } } @@ -1913,6 +1921,14 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { // restore qureg state initDebugState(qureg); } + + SECTION( "targets mismatch outcomes (C++ only)") { + + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, {0,1}, {0,1,1}), + ContainsSubstring("inconsistent") + ); + } } } @@ -2782,6 +2798,14 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { ); } + SECTION( "targets mismatch outcomes (C++ only)" ) { + + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, {0,1,2}, {0,1}), + ContainsSubstring("inconsistent") + ); + } + // projector does NOT validate outcome probability } } @@ -2876,6 +2900,14 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { ); } + SECTION( "targets mismatch outcomes (C++ only)" ) { + + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, {0,1,2}, {0,1}), + ContainsSubstring("inconsistent") + ); + } + // projector does NOT validate outcome probability } } From 59448ba6460a482dbc62b46104757a3ddbfa393a Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 20:26:26 -0500 Subject: [PATCH 18/20] updated doc warnings --- quest/include/multiplication.h | 3 --- quest/include/operations.h | 16 ---------------- quest/include/trotterisation.h | 8 ++------ 3 files changed, 2 insertions(+), 25 deletions(-) diff --git a/quest/include/multiplication.h b/quest/include/multiplication.h index b79a0555..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() diff --git a/quest/include/operations.h b/quest/include/operations.h index 1b0e2240..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); @@ -2340,8 +2334,6 @@ qindex applyMultiQubitMeasurement(Qureg qureg, std::vector qubits); qindex applyMultiQubitMeasurementAndGetProb(Qureg qureg, std::vector qubits, qreal* probability); -/// @notyettested -/// @notyetvalidated /// @notyetdoced /// @cppvectoroverload /// @see applyForcedMultiQubitMeasurement() @@ -2367,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); @@ -2384,8 +2374,6 @@ void applyMultiQubitProjector(Qureg qureg, int* qubits, int* outcomes, int numQu #ifdef __cplusplus -/// @notyettested -/// @notyetvalidated /// @notyetdoced /// @cppvectoroverload /// @see applyMultiQubitProjector() @@ -2411,12 +2399,10 @@ extern "C" { /// @notyetdoced -/// @notyetvalidated void applyQuantumFourierTransform(Qureg qureg, int* targets, int numTargets); /// @notyetdoced -/// @notyetvalidated void applyFullQuantumFourierTransform(Qureg qureg); @@ -2428,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..79bd319c 100644 --- a/quest/include/trotterisation.h +++ b/quest/include/trotterisation.h @@ -294,9 +294,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 @@ -389,9 +387,7 @@ extern "C" { 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. * From 3967fdc1a9ec3140d2762a9f896a76be45b89933 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Mon, 19 Jan 2026 20:31:05 -0500 Subject: [PATCH 19/20] added Vasco to Trotter API authorlist --- quest/include/trotterisation.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/quest/include/trotterisation.h b/quest/include/trotterisation.h index 79bd319c..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); @@ -383,6 +405,7 @@ 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); @@ -513,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); @@ -661,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); From bca94246ca51c008459c8548cbc3db5a472c27be Mon Sep 17 00:00:00 2001 From: Oliver Thomson Brown Date: Tue, 20 Jan 2026 16:28:41 +0000 Subject: [PATCH 20/20] merged Tyson's patches --- quest/include/multiplication.h | 22 +- quest/include/operations.h | 24 +- quest/include/trotterisation.h | 33 +- quest/src/api/operations.cpp | 5 + quest/src/core/validation.cpp | 2 +- tests/unit/initialisations.cpp | 7 +- tests/unit/operations.cpp | 532 ++++++++++++++++++++++++--------- tests/unit/trotterisation.cpp | 217 ++++++++------ tests/utils/cache.cpp | 16 + tests/utils/cache.hpp | 3 + 10 files changed, 599 insertions(+), 262 deletions(-) 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 8b7fc33f..a0a99086 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -1047,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) @@ -1138,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") ); } @@ -1414,12 +1413,12 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + Qureg qureg = getArbitraryCachedStatevec(); int targs[] = {0, 1, 2}; int numTargs = 3; SECTION( "qureg uninitialised" ) { + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1429,7 +1428,8 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { } SECTION( "invalid target qubits" ) { - int badTargs[] = {0, 1, 10}; + + int badTargs[] = {0, 1, qureg.numQubits}; // latter is too large REQUIRE_THROWS_WITH( applyQuantumFourierTransform(qureg, badTargs, 3), ContainsSubstring("target") @@ -1437,6 +1437,7 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { } SECTION( "duplicate target qubits" ) { + int dupTargs[] = {0, 1, 1}; REQUIRE_THROWS_WITH( applyQuantumFourierTransform(qureg, dupTargs, 3), @@ -1444,14 +1445,20 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { ); } - SECTION( "zero number of targets" ) { + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE_COPY( -1, 0 ); REQUIRE_THROWS_WITH( - applyQuantumFourierTransform(qureg, targs, 0), - ContainsSubstring("targets") + applyQuantumFourierTransform(qureg, targs, badNumTargs), + ContainsSubstring("targets") || ContainsSubstring("target qubits") ); - } - destroyQureg(qureg); + badNumTargs = qureg.numQubits+1; + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(qureg, targs, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } } } @@ -1499,10 +1506,10 @@ TEST_CASE( "applyFullQuantumFourierTransform", TEST_CATEGORY_OPS ) { SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1510,8 +1517,6 @@ TEST_CASE( "applyFullQuantumFourierTransform", TEST_CATEGORY_OPS ) { ContainsSubstring("invalid Qureg") ); } - - destroyQureg(qureg); } } @@ -1539,11 +1544,11 @@ TEST_CASE( "applyQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1553,14 +1558,24 @@ TEST_CASE( "applyQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( "invalid target qubit" ) { - // Try to project a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - applyQubitProjector(qureg, 10, 0), + applyQubitProjector(qureg, badTarget, 0), ContainsSubstring("target") ); } - destroyQureg(qureg); + SECTION( "invalid outcome" ) { + + int badOutcome = GENERATE_COPY( -1, 2 ); + REQUIRE_THROWS_WITH( + applyQubitProjector(qureg, 0, badOutcome), + ContainsSubstring("outcome") + ); + } + + // projector does NOT validate outcome probability } } @@ -1588,14 +1603,14 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); 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( @@ -1605,8 +1620,8 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { } 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}; + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; REQUIRE_THROWS_WITH( applyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), ContainsSubstring("target") @@ -1614,7 +1629,7 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { } 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), @@ -1622,15 +1637,39 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { ); } - SECTION( "zero number of targets" ) { - // Try to project no qubits at all (empty list) + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); REQUIRE_THROWS_WITH( - applyMultiQubitProjector(qureg, targets, outcomes, 0), + 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") + ); } - destroyQureg(qureg); + // projector does NOT validate outcome probability } } @@ -1671,11 +1710,17 @@ TEST_CASE( "applyForcedQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + 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" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1685,14 +1730,55 @@ TEST_CASE( "applyForcedQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( "invalid target qubit" ) { - // Try to measure a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - applyForcedQubitMeasurement(qureg, 10, 0), + applyForcedQubitMeasurement(qureg, badTarget, 0), ContainsSubstring("target") ); } - destroyQureg(qureg); + 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); + } } } @@ -1740,14 +1826,18 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + 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" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1757,8 +1847,8 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } 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}; + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits )}; REQUIRE_THROWS_WITH( applyForcedMultiQubitMeasurement(qureg, badTargets, outcomes, numTargets), ContainsSubstring("target") @@ -1766,7 +1856,7 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } 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), @@ -1774,15 +1864,71 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { ); } - SECTION( "zero number of targets" ) { - // Try to measure no qubits at all (empty list) + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); REQUIRE_THROWS_WITH( - applyForcedMultiQubitMeasurement(qureg, targets, outcomes, 0), + 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") + ); } - destroyQureg(qureg); + 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") + ); + } } } @@ -1821,13 +1967,13 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); 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( @@ -1837,8 +1983,8 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } 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}; + + int badTargets[] = {0, 1, GENERATE_COPY(-1, qureg.numQubits)}; REQUIRE_THROWS_WITH( applyMultiQubitMeasurement(qureg, badTargets, numTargets), ContainsSubstring("target") @@ -1846,7 +1992,7 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { } 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), @@ -1854,15 +2000,20 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { ); } - SECTION( "zero number of targets" ) { - // Try to measure no qubits at all (empty list) + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); REQUIRE_THROWS_WITH( - applyMultiQubitMeasurement(qureg, targets, 0), + applyMultiQubitMeasurement(qureg, targets, badNumTargs), ContainsSubstring("targets") ); - } - destroyQureg(qureg); + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, targets, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } } } @@ -1903,13 +2054,14 @@ TEST_CASE( "applyMultiQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); int targets[] = {0, 1, 2}; int numTargets = 3; + qreal outProb = 0; SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1919,35 +2071,37 @@ TEST_CASE( "applyMultiQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { } 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; + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits )}; REQUIRE_THROWS_WITH( - applyMultiQubitMeasurementAndGetProb(qureg, badTargets, numTargets, &prob), + applyMultiQubitMeasurementAndGetProb(qureg, badTargets, numTargets, &outProb), 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), + applyMultiQubitMeasurementAndGetProb(qureg, dupTargets, numTargets, &outProb), ContainsSubstring("duplicate") ); } - SECTION( "zero number of targets" ) { - // Try to measure no qubits at all (empty list) - qreal prob = 0; + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); REQUIRE_THROWS_WITH( - applyMultiQubitMeasurementAndGetProb(qureg, targets, 0, &prob), + applyMultiQubitMeasurementAndGetProb(qureg, targets, badNumTargs, &outProb), ContainsSubstring("targets") ); - } - destroyQureg(qureg); + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, targets, badNumTargs, &outProb), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } } } @@ -1985,11 +2139,11 @@ TEST_CASE( "applyQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -1999,14 +2153,13 @@ TEST_CASE( "applyQubitMeasurement", TEST_CATEGORY_OPS ) { } SECTION( "invalid target qubit" ) { - // Try to measure a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - applyQubitMeasurement(qureg, 10), + applyQubitMeasurement(qureg, badTarget), ContainsSubstring("target") ); } - - destroyQureg(qureg); } } @@ -2046,29 +2199,28 @@ TEST_CASE( "applyQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); + qreal outProb = 0; 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), + applyQubitMeasurementAndGetProb(badQureg, 0, &outProb), 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; + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - applyQubitMeasurementAndGetProb(qureg, 10, &prob), + applyQubitMeasurementAndGetProb(qureg, badTarget, &outProb), ContainsSubstring("target") ); } - - destroyQureg(qureg); } } @@ -2100,6 +2252,8 @@ 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 } @@ -2153,6 +2307,8 @@ TEST_CASE( "applyFullStateDiagMatrPower", TEST_CATEGORY_OPS LABEL_MIXED_DEPLOY_T setValidationEpsilonToDefault(); } + + /// @todo input validation } @@ -2182,12 +2338,12 @@ TEST_CASE( "applyNonUnitaryPauliGadget", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); 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( @@ -2196,7 +2352,7 @@ TEST_CASE( "applyNonUnitaryPauliGadget", TEST_CATEGORY_OPS ) { ); } - destroyQureg(qureg); + /// @todo remaining input validation } } @@ -2327,6 +2483,8 @@ 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 } @@ -2350,6 +2508,8 @@ TEST_CASE( "rightapplyFullStateDiagMatr", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_ TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc); } } + + /// @todo input validation } @@ -2392,6 +2552,8 @@ TEST_CASE( "leftapplyFullStateDiagMatrPower", TEST_CATEGORY_MULT LABEL_MIXED_DEP TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc); } } + + /// @todo input validation } @@ -2425,11 +2587,11 @@ TEST_CASE( "rightapplyFullStateDiagMatrPower", TEST_CATEGORY_MULT LABEL_MIXED_DE } } - + /// @todo input validation } -TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); @@ -2452,11 +2614,11 @@ TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -2466,19 +2628,29 @@ TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( "invalid target qubit" ) { - // Try to project a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - leftapplyQubitProjector(qureg, 10, 0), + leftapplyQubitProjector(qureg, badTarget, 0), ContainsSubstring("target") ); } - destroyQureg(qureg); + 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 ); @@ -2500,11 +2672,11 @@ TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createDensityQureg(numQubits); + + Qureg qureg = getArbitraryCachedDensmatr(); SECTION( "qureg uninitialised" ) { - // Invalidate the qureg by setting a negative qubit count + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -2513,20 +2685,39 @@ TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { ); } + SECTION( "qureg is not density matrix" ) { + + Qureg badQureg = getArbitraryCachedStatevec(); + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(badQureg, 0, 0), + ContainsSubstring("received a statevector") + ); + } + SECTION( "invalid target qubit" ) { - // Try to project a qubit that doesn't exist (qubit index 10 on a 5-qubit system) + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); REQUIRE_THROWS_WITH( - rightapplyQubitProjector(qureg, 10, 0), + rightapplyQubitProjector(qureg, badTarget, 0), ContainsSubstring("target") ); } - destroyQureg(qureg); + 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 ); @@ -2549,14 +2740,14 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); 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( @@ -2566,8 +2757,8 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { } 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}; + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; REQUIRE_THROWS_WITH( leftapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), ContainsSubstring("target") @@ -2575,7 +2766,7 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { } 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), @@ -2583,20 +2774,44 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { ); } - SECTION( "zero number of targets" ) { - // Try to project no qubits at all (empty list) + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); REQUIRE_THROWS_WITH( - leftapplyMultiQubitProjector(qureg, targets, outcomes, 0), + 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") + ); } - destroyQureg(qureg); + // 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 ); @@ -2618,14 +2833,14 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createDensityQureg(numQubits); + + Qureg qureg = getArbitraryCachedDensmatr(); 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( @@ -2634,9 +2849,18 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { ); } + 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" ) { - // Try to project qubits that don't exist (qubit index 10 on a 5-qubit system) - int badTargets[] = {0, 1, 10}; + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; REQUIRE_THROWS_WITH( rightapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), ContainsSubstring("target") @@ -2644,7 +2868,7 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { } 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), @@ -2652,15 +2876,39 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { ); } - SECTION( "zero number of targets" ) { - // Try to project no qubits at all (empty list) + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); REQUIRE_THROWS_WITH( - rightapplyMultiQubitProjector(qureg, targets, outcomes, 0), + 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") + ); } - destroyQureg(qureg); + // projector does NOT validate outcome probability } } @@ -2692,12 +2940,12 @@ TEST_CASE( "leftapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) { } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); 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); @@ -2708,8 +2956,9 @@ TEST_CASE( "leftapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) { destroyQureg(workspace); } - destroyQureg(qureg); destroyPauliStrSum(sum); + + /// @todo remaining input validation } } @@ -2740,12 +2989,12 @@ TEST_CASE( "rightapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); + + Qureg qureg = getArbitraryCachedStatevec(); 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); @@ -2756,8 +3005,9 @@ TEST_CASE( "rightapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) destroyQureg(workspace); } - destroyQureg(qureg); destroyPauliStrSum(sum); + + /// @todo remaining input validation } } diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index ca891f88..35af81ba 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -220,92 +220,101 @@ TEST_CASE( "randomisedTrotter", TEST_CATEGORY ) { */ 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) ); - } + 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); - // Verify state remains normalized - REQUIRE_THAT( calcTotalProb(qureg), WithinAbs(1.0, normEps) ); + REQUIRE_THAT( expec, WithinAbs(refObservables[i], obsEps) ); + } + + // Verify state remains normalized + REQUIRE_THAT( calcTotalProb(qureg), WithinAbs(1.0, normEps) ); - // Restore validation epsilon - setValidationEpsilon(initialValidationEps); + // Restore validation epsilon + setValidationEpsilon(initialValidationEps); - destroyQureg(qureg); - destroyPauliStrSum(hamil); - destroyPauliStrSum(observ); + destroyQureg(qureg); + destroyPauliStrSum(hamil); + destroyPauliStrSum(observ); } SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); - PauliStrSum hamil = createHeisenbergHamiltonian(numQubits); + Qureg qureg = getArbitraryCachedStatevec(); + PauliStrSum hamil = createHeisenbergHamiltonian(qureg.numQubits); bool permutePaulis = false; SECTION( "qureg uninitialised" ) { + Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( @@ -315,6 +324,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "pauli sum uninitialized" ) { + PauliStrSum badHamil = hamil; badHamil.numTerms = 0; REQUIRE_THROWS_WITH( @@ -323,18 +333,33 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { ); } + 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" ) { - Qureg smallQureg = createQureg(3); - PauliStrSum largeHamil = createHeisenbergHamiltonian(numQubits); + + PauliStrSum largeHamil = createHeisenbergHamiltonian(qureg.numQubits + 1); REQUIRE_THROWS_WITH( - applyTrotterizedUnitaryTimeEvolution(smallQureg, largeHamil, 0.1, 4, 5, permutePaulis), + applyTrotterizedUnitaryTimeEvolution(qureg, 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") @@ -342,6 +367,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter order (negative)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, -2, 5, permutePaulis), ContainsSubstring("order") @@ -349,6 +375,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter order (odd, not 1)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 3, 5, permutePaulis), ContainsSubstring("order") @@ -356,6 +383,7 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter reps (zero)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 4, 0, permutePaulis), ContainsSubstring("repetitions") @@ -363,13 +391,13 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter reps (negative)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 4, -3, permutePaulis), ContainsSubstring("repetitions") ); } - destroyQureg(qureg); destroyPauliStrSum(hamil); } } @@ -377,6 +405,14 @@ TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { 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; @@ -512,12 +548,12 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { SECTION( LABEL_VALIDATION ) { - int numQubits = 5; - Qureg qureg = createQureg(numQubits); - PauliStrSum ising = createIsingHamiltonian(numQubits, 1.0, 1.0, 0.0); + 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( @@ -527,6 +563,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "pauli sum uninitialized" ) { + PauliStrSum badIsing = ising; badIsing.numTerms = 0; REQUIRE_THROWS_WITH( @@ -536,17 +573,17 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "pauli sum exceeds qureg qubits" ) { - Qureg smallQureg = createQureg(3); - PauliStrSum largeIsing = createIsingHamiltonian(numQubits, 1.0, 1.0, 0.0); + + PauliStrSum largeIsing = createIsingHamiltonian(qureg.numQubits+1, 1.0, 1.0, 0.0); REQUIRE_THROWS_WITH( - applyTrotterizedImaginaryTimeEvolution(smallQureg, largeIsing, 0.1, 4, 5, permutePaulis), + applyTrotterizedImaginaryTimeEvolution(qureg, 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})); @@ -561,6 +598,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter order (zero)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 0, 5, permutePaulis), ContainsSubstring("order") @@ -568,6 +606,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter order (negative)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, -2, 5, permutePaulis), ContainsSubstring("order") @@ -575,6 +614,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter order (odd, not 1)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 3, 5, permutePaulis), ContainsSubstring("order") @@ -582,6 +622,7 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter reps (zero)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 4, 0, permutePaulis), ContainsSubstring("repetitions") @@ -589,13 +630,13 @@ TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { } SECTION( "invalid trotter reps (negative)" ) { + REQUIRE_THROWS_WITH( applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 4, -3, permutePaulis), ContainsSubstring("repetitions") ); } - destroyQureg(qureg); destroyPauliStrSum(ising); } } @@ -617,5 +658,3 @@ 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 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); 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();