The Quantum Exact Simulation Toolkit v4.3.0
Loading...
Searching...
No Matches
experimental.cpp
1/** @file
2 * Unit tests of the environment module.
3 *
4 * @author Oliver Brown
5 * @author Tyson Jones
6 * @author Ashmit JaiSarita Gupta (checkpoint test prototype)
7 *
8 * @defgroup unitexperi Experimental
9 * @ingroup unittests
10 */
11
12#include "quest.h"
13
14#include <catch2/catch_test_macros.hpp>
15#include <catch2/matchers/catch_matchers_string.hpp>
16#include <catch2/generators/catch_generators_range.hpp>
17
18#include "tests/utils/macros.hpp"
19#include "tests/utils/config.hpp"
20#include "tests/utils/cache.hpp"
21#include "tests/utils/compare.hpp"
22
23#include <filesystem>
24
25using Catch::Matchers::ContainsSubstring;
26
27
28
29/*
30 * UTILITIES
31 */
32
33#define TEST_CATEGORY \
34 LABEL_UNIT_TAG "[experimental]"
35
36
37void TEST_ON_CACHED_QUREGS(quregCache quregs, auto testFunc) {
38
39 for (auto& [label, qureg]: quregs) {
40
41 DYNAMIC_SECTION( label ) {
42
43 testFunc(qureg);
44 }
45 }
46}
47
48
49
50/**
51 * TESTS
52 *
53 * @ingroup unitexperi
54 * @{
55 */
56
57
58TEST_CASE( "setQuESTNumGpuThreadsPerBlock", TEST_CATEGORY ) {
59
60 // remember the default number for later restoration (hence static)
61 static int initNumTPB = getQuESTNumGpuThreadsPerBlock();
62
63 SECTION( LABEL_CORRECTNESS ) {
64
65 // begin at 64 (AMD min, larger than NVIDIA min of 32),
66 // stop at 1024 (should be less than dev-specific max)
67 int inNumTPB = GENERATE( 64, 128, 256, 512, 1024 );
69
70 int outNumTPB = getQuESTNumGpuThreadsPerBlock();
71 REQUIRE( inNumTPB == outNumTPB );
72
73 // BEWARE that we do not here test whether all QuEST
74 // operators succeed with the various numTBP; that must
75 // be ad hoc asssesed via updating the numTBP env-var
76 // before launching the entirety of the tests
77 }
78
79 SECTION( LABEL_VALIDATION ) {
80
81 SECTION( "Negative" ) {
82
83 int badNumTPB = GENERATE( 0, -1, -9999 );
84 REQUIRE_THROWS_WITH( setQuESTNumGpuThreadsPerBlock(badNumTPB), ContainsSubstring( "must be positive" ) );
85 }
86
87 SECTION( "Indivisible by warp size" ) {
88
89 // If HIP status was attached to QuESTEnv, we could do:
90 // QuESTEnv env = getQuESTEnv();
91 // int warpSize = (env.isGpuAccelerated && env.isHipCompiled)? 64 : 32;
92 // Since this currently isn't the case, we assume a warp size of 32,
93 // which will mean when this test is run on AMD GPUs, the below tested
94 // badNumTBP won't be as interestingly/rigorously spread
95 int warpSize = 32;
96
97 int badNumTPB = GENERATE_COPY( warpSize - 1, warpSize + 1, warpSize + warpSize/2, 3*warpSize + warpSize/2 );
98
99 REQUIRE_THROWS_WITH( setQuESTNumGpuThreadsPerBlock(badNumTPB), ContainsSubstring( "does not divide evenly into the warp size" ) );
100 }
101
102 SECTION( "Exceeds device maximum" ) {
103
104 int badNumTPB = 102400; // exceeds expected 1024 max
105
106 // Cannot be tested (since validation not imposed) when GPU is not actively used
107 if (getQuESTEnv().isGpuAccelerated)
108 REQUIRE_THROWS_WITH( setQuESTNumGpuThreadsPerBlock(badNumTPB), ContainsSubstring( "Exceeds the hardware-imposed maximum" ) );
109
110 SUCCEED( );
111 }
112 }
113
114 // restore numTBP, so as not to interfere with other tests
116}
117
118
119TEST_CASE( "getQuESTNumGpuThreadsPerBlock", TEST_CATEGORY ) {
120
121 SECTION( LABEL_CORRECTNESS ) {
122
123 // check initial value matches either the env-var (if set),
124 // or the fixed default in the codebase (hardcoded in test utils)
125 int defaultNum = getDefaultNumGpuThreadsPerBlock(); // test util via env-var
126 int reportedNum = getQuESTNumGpuThreadsPerBlock(); // QuEST API
127
128 REQUIRE( defaultNum == reportedNum );
129
130 // further testing of this function appears in setQuESTNumGpuThreadsPerBlock()
131 }
132
133 SECTION( LABEL_VALIDATION ) {
134
135 // there is none (except untestable env is init!)
136 SUCCEED( );
137 }
138}
139
140
141TEST_CASE( "saveQuregToFile", TEST_CATEGORY ) {
142
143 SECTION( LABEL_CORRECTNESS ) {
144
145 const char* outFn = "test_checkpoint.bp";
146
147 auto testFunc = [&](Qureg qureg) {
148 initRandomPureState(qureg);
149 REQUIRE_NOTHROW( saveQuregToFile(qureg, outFn) );
150
151 // note that we are NOT validating the contents was correct;
152 // that will be performed by the createQuregFromFile() test
153 };
154
155 // skip correctness tests if ADIOS2 not compiled
156 SECTION( LABEL_STATEVEC ) { if (QUEST_COMPILE_ADIOS2) TEST_ON_CACHED_QUREGS(getCachedStatevecs(), testFunc); SUCCEED( ); }
157 SECTION( LABEL_DENSMATR ) { if (QUEST_COMPILE_ADIOS2) TEST_ON_CACHED_QUREGS(getCachedDensmatrs(), testFunc); SUCCEED( ); }
158
159 // Single process deletes checkpoint file (assumes a shared filesystem; if not, who cares about the scraps?)
160 // Note these syncs are ESSENTIAL for correct behaviour, else root can begin deletion while a subsequent node
161 // proceeds to the below validation and re-creates some files within the same direc, causing MPI hangs. Ouch!
162 syncQuESTEnv();
163 if (getQuESTEnv().rank == 0)
164 std::filesystem::remove_all(outFn);
165 syncQuESTEnv();
166 }
167
168 SECTION( LABEL_VALIDATION ) {
169
170 Qureg qureg = getArbitraryCachedStatevec();
171
172 SECTION( "adios2 not compiled" ) {
173
174 if (!QUEST_COMPILE_ADIOS2)
175 REQUIRE_THROWS_WITH( saveQuregToFile(qureg, "dummy.bp"), ContainsSubstring("compiled with ADIOS2") );
176
177 SUCCEED( );
178 }
179
180 SECTION( "qureg uninitialised" ) {
181
182 if (QUEST_COMPILE_ADIOS2) {
183 Qureg badQureg;
184 badQureg.numQubits = -123;
185 REQUIRE_THROWS_WITH( saveQuregToFile(badQureg, "dummy.bp"), ContainsSubstring("Received an invalid Qureg") );
186 }
187
188 SUCCEED( );
189 }
190
191 SECTION( "bad name" ) {
192
193 if (QUEST_COMPILE_ADIOS2) {
194 // surprisingly hard to find cross-OS illegal names!
195 #if defined(_MSC_VER)
196 auto badFn = GENERATE( ":", "?", "*" );
197 #else
198 auto badFn = GENERATE( "", "\0" );
199 #endif
200 REQUIRE_THROWS_WITH( saveQuregToFile(qureg, badFn), ContainsSubstring("could not be opened") );
201 }
202
203 SUCCEED( );
204 }
205 }
206}
207
208
209TEST_CASE( "createQuregFromFile", TEST_CATEGORY ) {
210
211 SECTION( LABEL_CORRECTNESS ) {
212
213 const char* checkpointFn = "test_checkpoint.bp";
214
215 // We will iterate the cached Quregs so the save path is exercised under every
216 // deployment combination (serial, OMP, MPI, GPU and their mixtures). However,
217 // the restored Qureg uses a distribution chosen by the auto-deployer, which is
218 // not permitted to differ from the checkpointed distribution. We know, given
219 // the unit test Quregs are so small, that distribution is NEVER automatically
220 // enabled; so we will forbid testing with distributed Quregs
221 int legalNumNodes = 1;
222
223 auto testFunc = [&](Qureg qureg) {
224
225 initRandomPureState(qureg);
226 REQUIRE_NOTHROW( saveQuregToFile(qureg, checkpointFn) );
227
228 // skip restoration when new Qureg distribution would disagree with old
229 if (qureg.numNodes != legalNumNodes)
230 return;
231
232 Qureg newQureg = createQuregFromFile(checkpointFn);
233 REQUIRE_AGREE(qureg, newQureg);
234
235 destroyQureg(newQureg);
236 };
237
238 // skip correctness tests if ADIOS2 not compiled
239 SECTION( LABEL_STATEVEC ) { if (QUEST_COMPILE_ADIOS2) TEST_ON_CACHED_QUREGS(getCachedStatevecs(), testFunc); SUCCEED( ); }
240 SECTION( LABEL_DENSMATR ) { if (QUEST_COMPILE_ADIOS2) TEST_ON_CACHED_QUREGS(getCachedDensmatrs(), testFunc); SUCCEED( ); }
241
242 CAPTURE( checkpointFn );
243
244 // Single process deletes checkpoint file (assumes a shared filesystem; if not, who cares about the scraps?).
245 // Note these syncs are ESSENTIAL for correct behaviour, else root can begin deletion while a subsequent node
246 // proceeds to the below validation and re-creates some files within the same direc, causing MPI hangs. Ouch!
247 syncQuESTEnv();
248 if (getQuESTEnv().rank == 0)
249 std::filesystem::remove_all(checkpointFn);
250 syncQuESTEnv();
251 }
252
253 SECTION( LABEL_VALIDATION ) {
254
255 SECTION( "adios2 not compiled" ) {
256
257 if (!QUEST_COMPILE_ADIOS2)
258 REQUIRE_THROWS_WITH( createQuregFromFile("dummy.bp"), ContainsSubstring("compiled with ADIOS2") );
259
260 SUCCEED( );
261 }
262
263 SECTION( "bad name" ) {
264
265 if (QUEST_COMPILE_ADIOS2)
266 REQUIRE_THROWS_WITH( createQuregFromFile("BAD_FILENAME"), ContainsSubstring("could not be opened") );
267
268 SUCCEED( );
269 }
270
271 SECTION( "differing distributions" ) {
272
273 // Distributions can only differ when QuEST is distributed over more than 1 node
274 if (QUEST_COMPILE_ADIOS2 && getQuESTEnv().numNodes > 1) {
275
276 // Create a new distributed qureg; we know createQuregFromFile() will create
277 // non-distributed, since unit-test-size Quregs auto-deploy to non-distributed
278 Qureg quregDistrib = createCustomQureg(getNumCachedQubits(), 0, /*useDistrib=*/1, 0, 0);
279
280 CAPTURE( quregDistrib.numNodes );
281
282 // Write qureg to file, then deliberately fail to restore it
283 const char* fn = "test_checkpoint.bp";
284 saveQuregToFile(quregDistrib, fn);
285 REQUIRE_THROWS_WITH( createQuregFromFile(fn), ContainsSubstring("distributions must match") );
286
287 // cleanup
288 destroyQureg(quregDistrib);
289 syncQuESTEnv();
290 if (getQuESTEnv().rank == 0)
291 std::filesystem::remove_all(fn);
292 syncQuESTEnv();
293 }
294
295 SUCCEED( );
296 }
297
298 // We do not presently test the below validations, since it will require
299 // externally generating and saving ADIOS2 files; quite a pain!
300 // SECTION( "differing precision" ) { }
301 // SECTION( "overflow" ) { }
302 // SECTION( "insufficient RAM" ) { }
303 }
304}
305
306
307/** @} (end defgroup) */
308
309
310
311/**
312 * @todo
313 * UNTESTED FUNCTIONS
314 */
315
316// nothing! :^)
QuESTEnv getQuESTEnv()
void syncQuESTEnv()
Qureg createQuregFromFile(const char *fn)
int getQuESTNumGpuThreadsPerBlock()
void setQuESTNumGpuThreadsPerBlock(int numThreadsPerBlock)
void saveQuregToFile(Qureg qureg, const char *fn)
void initRandomPureState(Qureg qureg)
Qureg createCustomQureg(int numQubits, int isDensMatr, int useDistrib, int useGpuAccel, int useMultithread)
Definition qureg.cpp:283
void destroyQureg(Qureg qureg)
Definition qureg.cpp:340
TEST_CASE("setQuESTNumGpuThreadsPerBlock", TEST_CATEGORY)
Definition qureg.h:49