Skip to content

Commit

Permalink
added Windows unit tests
Browse files Browse the repository at this point in the history
in addition to the existing CI build test.

This patch involved accelerating some tests to avoid timeouts, and replicating (in a Catch2 v2 compatible way) the Catch2 patch for this Windows-specific issue:
catchorg/Catch2#2040
  • Loading branch information
TysonRayJones committed Aug 7, 2023
1 parent a54fc0d commit 206adf4
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Expand Up @@ -33,7 +33,7 @@ jobs:
working-directory: build_dir

- name: cmake test
run: ctest -j2 --output-on-failure --timeout 5000 -E mixMultiQubitKrausMap
run: ctest -j2 --output-on-failure --timeout 5000
working-directory: build_dir

- name: upload coverage results
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/llvm-asan.yml
Expand Up @@ -33,5 +33,5 @@ jobs:
working-directory: build_dir

- name: cmake test
run: ctest -j2 --output-on-failure --timeout 5000 -E mixMultiQubitKrausMap
run: ctest -j2 --output-on-failure --timeout 5000
working-directory: build_dir
2 changes: 1 addition & 1 deletion .github/workflows/macos-unit.yml
Expand Up @@ -33,5 +33,5 @@ jobs:
working-directory: build_dir

- name: cmake test
run: ctest -j2 --output-on-failure -E mixMultiQubitKrausMap
run: ctest -j2 --output-on-failure
working-directory: build_dir
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu-unit.yml
Expand Up @@ -33,5 +33,5 @@ jobs:
working-directory: build_dir

- name: cmake test
run: ctest -j2 --output-on-failure -E mixMultiQubitKrausMap
run: ctest -j2 --output-on-failure
working-directory: build_dir
40 changes: 40 additions & 0 deletions .github/workflows/windows-unit.yml
@@ -0,0 +1,40 @@
name: Windows unit

on:
push:
branches:
- master
- develop
pull_request:
branches:
- '**'

jobs:

build-and-test:
name: Unit tests on Windows MSVC
runs-on: windows-latest
strategy:
matrix:
float-precision: [2]

steps:
- uses: actions/checkout@v2
- uses: ilammy/msvc-dev-cmd@v1

- name: make build directory
shell: cmd
run: mkdir build_dir

- name: cmake configure
shell: cmd
run: cmake .. -DTESTING=ON -DPRECISION:STRING=${{matrix.float-precision}}
working-directory: build_dir

- name: cmake build
run: cmake --build . --target tests
working-directory: build_dir

- name: cmake test
run: ctest -j2 --output-on-failure
working-directory: build_dir
53 changes: 35 additions & 18 deletions tests/catch/catch.hpp
Expand Up @@ -3961,7 +3961,12 @@ namespace Generators {
class SingleValueGenerator final : public IGenerator<T> {
T m_value;
public:
SingleValueGenerator(T&& value) : m_value(std::move(value)) {}
SingleValueGenerator(T const& value) :
m_value(value)
{}
SingleValueGenerator(T&& value) :
m_value(std::move(value))
{}

T const& get() const override {
return m_value;
Expand Down Expand Up @@ -4005,9 +4010,11 @@ namespace Generators {
}
};

template <typename T>
GeneratorWrapper<T> value(T&& value) {
return GeneratorWrapper<T>(pf::make_unique<SingleValueGenerator<T>>(std::forward<T>(value)));
template <typename T, typename DecayedT = std::decay_t<T>>
GeneratorWrapper<DecayedT> value( T&& value ) {
return GeneratorWrapper<DecayedT>(
pf::make_unique<SingleValueGenerator<DecayedT>>(
std::forward<T>( value ) ) );
}
template <typename T>
GeneratorWrapper<T> values(std::initializer_list<T> values) {
Expand All @@ -4019,27 +4026,36 @@ namespace Generators {
std::vector<GeneratorWrapper<T>> m_generators;
size_t m_current = 0;

void populate(GeneratorWrapper<T>&& generator) {
m_generators.emplace_back(std::move(generator));
void add_generator( GeneratorWrapper<T>&& generator ) {
m_generators.emplace_back( std::move( generator ) );
}
void populate(T&& val) {
m_generators.emplace_back(value(std::forward<T>(val)));
void add_generator( T const& val ) {
m_generators.emplace_back( value( val ) );
}
template<typename U>
void populate(U&& val) {
populate(T(std::forward<U>(val)));
void add_generator( T&& val ) {
m_generators.emplace_back( value( std::move( val ) ) );
}
template<typename U, typename... Gs>
void populate(U&& valueOrGenerator, Gs &&... moreGenerators) {
populate(std::forward<U>(valueOrGenerator));
populate(std::forward<Gs>(moreGenerators)...);
template <typename U>
std::enable_if_t<!std::is_same<std::decay_t<U>, T>::value>
add_generator( U&& val ) {
add_generator( T( std::forward<U>( val ) ) );
}

template <typename U> void add_generators( U&& valueOrGenerator ) {
add_generator( std::forward<U>( valueOrGenerator ) );
}

template <typename U, typename... Gs>
void add_generators( U&& valueOrGenerator, Gs&&... moreGenerators ) {
add_generator( std::forward<U>( valueOrGenerator ) );
add_generators( std::forward<Gs>( moreGenerators )... );
}

public:
template <typename... Gs>
Generators(Gs &&... moreGenerators) {
m_generators.reserve(sizeof...(Gs));
populate(std::forward<Gs>(moreGenerators)...);
add_generators(std::forward<Gs>(moreGenerators)...);
}

T const& get() const override {
Expand All @@ -4059,7 +4075,8 @@ namespace Generators {
};

template<typename... Ts>
GeneratorWrapper<std::tuple<Ts...>> table( std::initializer_list<std::tuple<typename std::decay<Ts>::type...>> tuples ) {
GeneratorWrapper<std::tuple<std::decay_t<Ts>...>>
table( std::initializer_list<std::tuple<std::decay_t<Ts>...>> tuples ) {
return values<std::tuple<Ts...>>( tuples );
}

Expand All @@ -4076,7 +4093,7 @@ namespace Generators {
return Generators<T>(std::move(generator));
}
template<typename T, typename... Gs>
auto makeGenerators( T&& val, Gs &&... moreGenerators ) -> Generators<T> {
auto makeGenerators( T&& val, Gs &&... moreGenerators ) -> Generators<std::decay_t<T>> {
return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... );
}
template<typename T, typename U, typename... Gs>
Expand Down
4 changes: 4 additions & 0 deletions tests/test_calculations.cpp
Expand Up @@ -1428,6 +1428,10 @@ TEST_CASE( "getNumQubits", "[calculations]" ) {
}
SECTION( "density-matrix" ) {

// density matrices use square as much memory; we must be careful not to seg-fault!
if (2*numQb > 25)
numQb = 13; // max size

Qureg mat = createDensityQureg(numQb, QUEST_ENV);
REQUIRE( getNumQubits(mat) == numQb );
destroyQureg(mat, QUEST_ENV);
Expand Down
17 changes: 13 additions & 4 deletions tests/test_decoherence.cpp
Expand Up @@ -240,10 +240,15 @@ TEST_CASE( "mixMultiQubitKrausMap", "[decoherence]" ) {
* and a heap overhead when numTargs >= 4
*/

// try every size (qubit wise) map
int numTargs = GENERATE_COPY( range(1,maxNumTargs+1) ); // inclusive upper bound

// note this is very expensive to try every arrangement (2 min runtime for numTargs=5 alone)
int* targs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), numTargs) );
// previously, we tried every unique set of targets, via:
// int* targs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), numTargs) );
// alas, this is too slow for CI, so we instead try a fixed number of random sets:
GENERATE( range(0, 10) );
VLA(int, targs, numTargs);
setRandomTargets(targs, numTargs, NUM_QUBITS);

// try the min and max number of operators, and 2 random numbers
// (there are way too many to try all!)
Expand Down Expand Up @@ -671,8 +676,12 @@ TEST_CASE( "mixNonTPMultiQubitKrausMap", "[decoherence]" ) {

int numTargs = GENERATE_COPY( range(1,maxNumTargs+1) ); // inclusive upper bound

// note this is very expensive to try every arrangement (2 min runtime for numTargs=5 alone)
int* targs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), numTargs) );
// previously, we tried every unique set of targets, via:
// int* targs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), numTargs) );
// alas, this is too slow for CI, so we instead try a fixed number of random sets:
GENERATE( range(0, 10) );
VLA(int, targs, numTargs);
setRandomTargets(targs, numTargs, NUM_QUBITS);

// try the min and max number of operators, and 2 random numbers
// (there are way too many to try all!)
Expand Down
18 changes: 14 additions & 4 deletions tests/test_operators.cpp
Expand Up @@ -1950,8 +1950,13 @@ TEST_CASE( "applyParamNamedPhaseFunc", "[operators]" ) {
minTotalQubits = 2*numRegs;
totalNumQubits = GENERATE_COPY( range(minTotalQubits,NUM_QUBITS+1) );

// try every qubits subset and ordering
int* regs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), totalNumQubits) );
// Previously, we would try every qubits subset and ordering, via:
// int* regs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), totalNumQubits) );
// Alas, this is too many tests and times out Ubuntu unit testing. So instead, we
// randomly choose some qubits, 10 times.
GENERATE( range(0, 10) );
VLA(int, regs, totalNumQubits);
setRandomTargets(regs, totalNumQubits, NUM_QUBITS);

// assign each sub-reg its minimum length
int unallocQubits = totalNumQubits;
Expand Down Expand Up @@ -2530,8 +2535,13 @@ TEST_CASE( "applyParamNamedPhaseFuncOverrides", "[operators]" ) {
minTotalQubits = 2*numRegs;
totalNumQubits = GENERATE_COPY( range(minTotalQubits,NUM_QUBITS+1) );

// try every qubits subset and ordering
int* regs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), totalNumQubits) );
// Previously, we would try every qubits subset and ordering, via:
// int* regs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), totalNumQubits) );
// Alas, this is too many tests and times out Ubuntu unit testing. So instead, we
// randomly choose some qubits, 10 times.
GENERATE( range(0, 10) );
VLA(int, regs, totalNumQubits);
setRandomTargets(regs, totalNumQubits, NUM_QUBITS);

// assign each sub-reg its minimum length
int unallocQubits = totalNumQubits;
Expand Down
11 changes: 9 additions & 2 deletions tests/test_state_initialisations.cpp
Expand Up @@ -459,13 +459,20 @@ TEST_CASE( "setDensityAmps", "[state_initialisations]" ) {

SECTION( "density-matrix" ) {

// all valid number of amplitudes and offsets
// try all valid number of amplitudes and offsets
int startRow = GENERATE_COPY( range(0,maxInd) );
int startCol = GENERATE_COPY( range(0,maxInd) );

// determine the max number of amps that can be passed from the given start indices
int numPriorAmps = startRow + startCol*(1 << matr.numQubitsRepresented);
int maxNumAmps = matr.numAmpsTotal - numPriorAmps;
int numAmps = GENERATE_COPY( range(0,maxNumAmps) ); // upper-bound allows all amps specified

// previously, we tried all possible number of amps, like so:
// int numAmps = GENERATE_COPY( range(0,maxNumAmps) ); // upper-bound allows all amps specified
// but this is too many and causes a timeout on Ubuntu.
// So we instead randomly choose the number of amps, and repeat 10 times.
int numAmps = getRandomInt(0, maxNumAmps);
GENERATE_COPY( range(0,10) );

// generate random amplitudes
for (int i=0; i<numAmps; i++) {
Expand Down
2 changes: 1 addition & 1 deletion tests/test_unitaries.cpp
Expand Up @@ -2773,7 +2773,7 @@ TEST_CASE( "tGate", "[unitaries]" ) {

tGate(quregMatr, target);
applyReferenceOp(refMatr, target, op);
REQUIRE( areEqual(quregMatr, refMatr) );
REQUIRE( areEqual(quregMatr, refMatr, 1E2*REAL_EPS) );
}
}
SECTION( "input validation" ) {
Expand Down
36 changes: 29 additions & 7 deletions tests/utilities.cpp
Expand Up @@ -386,6 +386,7 @@ QMatrix getRandomQMatrix(int dim) {
// generate 2 normally-distributed random numbers via Box-Muller
qreal a = rand()/(qreal) RAND_MAX;
qreal b = rand()/(qreal) RAND_MAX;
if (a == 0) a = REAL_EPS; // prevent rand()=0 creation of NaN
qreal r1 = sqrt(-2 * log(a)) * cos(2 * 3.14159265 * b);
qreal r2 = sqrt(-2 * log(a)) * sin(2 * 3.14159265 * b);

Expand Down Expand Up @@ -961,12 +962,15 @@ bool areEqual(Qureg qureg, QMatrix matr, qreal precision) {

// DEBUG
if (!ampsAgree) {

// debug
char buff[200];
sprintf(buff, "[msg from utilities.cpp] node %d has a disagreement at (global) index %lld of (%s) + i(%s)\n",
qureg.chunkId, globalInd, REAL_STRING_FORMAT, REAL_STRING_FORMAT);
printf(buff, realDif, imagDif);
sprintf(buff, "[msg from utilities.cpp] node %d has a disagreement at %lld of (%s) + i(%s):\n\t[qureg] %s + i(%s) VS [ref] %s + i(%s)\n",
qureg.chunkId, startInd+i,
REAL_STRING_FORMAT, REAL_STRING_FORMAT, REAL_STRING_FORMAT,
REAL_STRING_FORMAT, REAL_STRING_FORMAT, REAL_STRING_FORMAT);
printf(buff,
realDif, imagDif,
qureg.stateVec.real[i], qureg.stateVec.imag[i],
real(matr[row][col]), imag(matr[row][col]));
}

// break loop as soon as amplitudes disagree
Expand Down Expand Up @@ -1109,7 +1113,7 @@ QMatrix toQMatrix(Qureg qureg) {
#endif

// copy full state vector into a QVector
long long int dim = (1 << qureg.numQubitsRepresented);
long long int dim = (1LL << qureg.numQubitsRepresented);
QMatrix matr = getZeroMatrix(dim);
for (long long int n=0; n<qureg.numAmpsTotal; n++)
matr[n%dim][n/dim] = qcomp(fullRe[n], fullIm[n]);
Expand Down Expand Up @@ -1232,7 +1236,7 @@ void toQureg(Qureg qureg, QVector vec) {
}
void toQureg(Qureg qureg, QMatrix mat) {
DEMAND( qureg.isDensityMatrix );
DEMAND( (1 << qureg.numQubitsRepresented) == (long long int) mat.size() );
DEMAND( (1LL << qureg.numQubitsRepresented) == (long long int) mat.size() );

syncQuESTEnv(QUEST_ENV);

Expand Down Expand Up @@ -1269,6 +1273,24 @@ void setRandomDiagPauliHamil(PauliHamil hamil) {
}
}

void setRandomTargets(int* targs, int numTargs, int numQb) {
DEMAND( numQb >= 1 );
DEMAND( numTargs >= 1);
DEMAND( numTargs <= numQb );

// create an ordered list of all possible qubits
VLA(int, allQb, numQb);
for (int q=0; q<numQb; q++)
allQb[q] = q;

// shuffle all qubits
std::random_shuffle(&allQb[0], &allQb[numQb]);

// select numTargs of all qubits
for (int i=0; i<numTargs; i++)
targs[i] = allQb[i];
}

QMatrix toQMatrix(qreal* coeffs, pauliOpType* paulis, int numQubits, int numTerms) {

// produce a numTargs-big matrix 'pauliSum' by pauli-matrix tensoring and summing
Expand Down
8 changes: 8 additions & 0 deletions tests/utilities.hpp
Expand Up @@ -1011,6 +1011,14 @@ void setRandomPauliSum(PauliHamil hamil);
*/
void setRandomDiagPauliHamil(PauliHamil hamil);

/** Populates \p targs with a random selection of \p numTargs elements from [0,\p numQb-1].
* List \p targs does not need to be initialised and its elements are overwritten.
*
* @ingroup testutilities
* @author Tyson Jones
*/
void setRandomTargets(int* targs, int numTargs, int numQb);

/** Returns the two's complement signed encoding of the unsigned number decimal,
* which must be a number between 0 and 2^numBits (exclusive). The returned number
* lies in [-2^(numBits-1), 2^(numBits-1)-1]
Expand Down

0 comments on commit 206adf4

Please sign in to comment.