diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index e277af5a3..71db5fa98 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -43,7 +43,8 @@ jobs: startsWith(matrix.os, 'macos' ) && 'MacOS' || startsWith(matrix.os, 'windows') && 'Windows' || 'Unknown' }} - ${{ endsWith(matrix.os, 'intel') && '(Intel)' || '' }} + ${{ endsWith(matrix.os, '15-intel') && '(Intel 2015)' || '' }} + ${{ endsWith(matrix.os, '26-intel') && '(Intel 2026)' || '' }} [${{ matrix.precision }}] ${{ matrix.omp == 'ON' && 'OMP' || '' }} ${{ matrix.mpi == 'ON' && 'MPI' || '' }} diff --git a/.github/workflows/test_edgecases.yaml b/.github/workflows/test_edgecases.yaml new file mode 100644 index 000000000..014c2f1a3 --- /dev/null +++ b/.github/workflows/test_edgecases.yaml @@ -0,0 +1,111 @@ +# Some free, ad hoc tests which test only specific +# functions in specific regimes, covering edge cases. +# These are intended as a stop gap in the interim to +# an improved test harness. +# +# We here test only CPU functions which have +# template parameters (like NumCtrls or NumTargs) +# which inform an optimised definition for MORE than +# 5 involved qubits. For example, multiControlledSWAP +# has optimised treatment of up to 5 control qubits, +# while needing 2 target qubits; so requires a Qureg +# of at least 8 qubits to trigger its unoptimised +# version (receiving 6 control qubits and 2 targets). +# +# We alas do NOT here test the fully-unoptimised +# versions of multi-ctrl multi-targ functions (such +# as applyMultiControlledCompMatr), because that +# requires reaching 6 ctrls + 6 targs, and ergo a +# Qureg of at least 12 qubits. So large a Hilbert +# space is alas too slow for the reference maths of +# our unit tests. Curse ye, exponential! +# +# @author Tyson Jones + +name: test (edge cases) + + +on: + push: + branches: + - main + - devel + pull_request: + branches: + - main + - devel + + +jobs: + + # run only some non-parallelised v4 unit tests at default (double) precision + serial-unit-test: + name: > + ${{ startsWith(matrix.os, 'ubuntu' ) && 'Linux' || + startsWith(matrix.os, 'macos' ) && 'MacOS' || + startsWith(matrix.os, 'windows') && 'Windows' || + 'Unknown' }} + ${{ endsWith(matrix.os, '26-intel') && '(Intel 2026)' || '' }} + serial + ${{ matrix.bmi2 == 'ON' && '(BMI)' || '' }} + edgecases + + runs-on: ${{ matrix.os }} + + strategy: + # continue other jobs if any fail + fail-fast: false + + # we will compile QuEST with all precisions but no parallelisation (though with Intel BMI2) + matrix: + os: [ubuntu-latest, macos-latest, windows-latest, macos-26-intel] + bmi2: [ON, OFF] + + exclude: + # cannot use BMI2 on non-Intel MacOS + - bmi2: ON + os: macos-latest + + # constants; all tests are non-comprehensive for speed + env: + build_dir: "build" + full_tests_regex: "Swap|CompMatr1|CompMatr2" + partial_tests_regex: "^applyMultiStateControlled(CompMatr|DiagMatr|PauliStr|PauliGadget)$" + QUEST_TEST_NUM_QUBITS_IN_QUREG: 8 + QUEST_TEST_MAX_NUM_QUBIT_PERMUTATIONS: 1 + QUEST_TEST_NUM_MIXED_DEPLOYMENT_REPETITIONS: 1 + + # perform the job + steps: + - name: Get QuEST + uses: actions/checkout@main + + # compile serial unit tests + - name: Configure CMake + run: > + cmake -B ${{ env.build_dir }} + -DQUEST_BUILD_TESTS=ON + -DQUEST_ENABLE_OMP=OFF + -DQUEST_ENABLE_BMI2=${{ matrix.bmi2 }} + + # force 'Release' build (needed by MSVC to enable optimisations) + - name: Compile + run: cmake --build ${{ env.build_dir }} --config Release + + # test statevector and density-matrix functions which exhibit + # optimisations on up to 5 ctrl + 2 target qubits, with 8-qubit + # Quregs, in order to test the non-optimised implementation + - name: 8-qubit edge-cases (full coverage) + run: ctest -C Release -R "${{ env.full_tests_regex }}" + working-directory: ${{ env.build_dir }} + + # it is our desire to test the "partial_tests" functions with + # 12-qubit Quregs, so that NumCtrls=6 while NumTargs=6 could be + # achieved, testing the fully unoptimised versions - but alas + # it's intractable for our slow reference maths! So we instead + # at least test the NumCtrls=6 and NumTargs=6 cases independently, + # requiring only 7-qubit Quregs, but we use 8 for consistency + # with above + - name: 8-qubit edge-cases (partial coverage) + run: ctest -C Release -R "${{ env.partial_tests_regex }}" + working-directory: ${{ env.build_dir }} diff --git a/.github/workflows/test_free.yml b/.github/workflows/test_free.yml index 6e7974338..ffafa197f 100644 --- a/.github/workflows/test_free.yml +++ b/.github/workflows/test_free.yml @@ -31,7 +31,8 @@ jobs: startsWith(matrix.os, 'macos' ) && 'MacOS' || startsWith(matrix.os, 'windows') && 'Windows' || 'Unknown' }} - ${{ endsWith(matrix.os, 'intel') && '(Intel)' || '' }} + ${{ endsWith(matrix.os, '15-intel') && '(Intel 2015)' || '' }} + ${{ endsWith(matrix.os, '26-intel') && '(Intel 2026)' || '' }} [${{ matrix.precision }}] serial ${{ matrix.bmi2 == 'ON' && '(BMI)' || '' }} diff --git a/tests/utils/linalg.cpp b/tests/utils/linalg.cpp index e8285cdff..1a1f8ed7d 100644 --- a/tests/utils/linalg.cpp +++ b/tests/utils/linalg.cpp @@ -15,6 +15,7 @@ #include #include +#include using std::vector; @@ -93,14 +94,15 @@ qindex setBitsAt(qindex num, vector inds, qindex bits) { } -int getNumPermutations(int n, int k) { +qindex getNumPermutations(int n, int k) { DEMAND( n >= k ); - DEMAND( n <= 11 ); // else int overflow + + constexpr auto max = std::numeric_limits::max(); // P(n, k) = n! / (n-k)! qindex p = 1; for (int t=n-k+1; t<=n; t++) - p *= t; + p *= (p < max / t)? t : 0; // set to 0 on overflow return p; } diff --git a/tests/utils/linalg.hpp b/tests/utils/linalg.hpp index 18555bf6f..4afa5f3e1 100644 --- a/tests/utils/linalg.hpp +++ b/tests/utils/linalg.hpp @@ -20,7 +20,6 @@ #include using std::vector; -int getNumPermutations(int n, int k); int getLog2(qindex); int getBitAt(qindex num, int ind); vector getBits(qindex num, int numBits); @@ -28,6 +27,7 @@ qindex getBitsAt(qindex num, vector inds); qindex setBitAt(qindex num, int ind, int bit); qindex setBitsAt(qindex num, vector inds, qindex bits); qindex getPow2(int); +qindex getNumPermutations(int n, int k); // =0 on overflow qreal getSum(vector vec); qcomp getSum(qvector); diff --git a/tests/utils/lists.cpp b/tests/utils/lists.cpp index b7ea3c340..62946617f 100644 --- a/tests/utils/lists.cpp +++ b/tests/utils/lists.cpp @@ -264,7 +264,7 @@ listpair GENERATE_CTRLS_AND_TARGS(int numQubits, int numCtrls, int numTargs) { DEMAND( numQubits >= numCtrls + numTargs ); // impose a limit on the number of {ctrls,targs} to generate (max-int if none set) - int numPerms = getNumPermutations(numQubits, numCtrls + numTargs); + auto numPerms = getNumPermutations(numQubits, numCtrls + numTargs); // 0 when overflowed int maxPerms = getMaxNumTestedQubitPermutations(); if (maxPerms == 0) maxPerms = std::numeric_limits::max(); @@ -272,7 +272,7 @@ listpair GENERATE_CTRLS_AND_TARGS(int numQubits, int numCtrls, int numTargs) { // if all permutations are permitted, determinstically generate each in turn. // note this wastefully generates all orderings of ctrl qubits, despite that // this has no effect on all API operations, but we carefully check anyway! - if (numPerms < maxPerms) + if (numPerms != 0 && numPerms < maxPerms) return GENERATE_COPY( disjointsublists(range(0,numQubits), numCtrls, numTargs) ); // otherwise generate as many random {ctrls,targs} as permitted