The Quantum Exact Simulation Toolkit v4.3.0
Loading...
Searching...
No Matches
qureg.cpp
1/** @file
2 * API definitions for creating and managing Quregs,
3 * and automatically choosing their deployment modes.
4 *
5 * @author Tyson Jones
6 */
7
8#include "quest/include/qureg.h"
9#include "quest/include/modes.h"
10#include "quest/include/environment.h"
11#include "quest/include/initialisations.h"
12
13#include "quest/src/core/validation.hpp"
14#include "quest/src/core/autodeployer.hpp"
15#include "quest/src/core/printer.hpp"
16#include "quest/src/core/bitwise.hpp"
17#include "quest/src/core/memory.hpp"
18#include "quest/src/core/utilities.hpp"
19#include "quest/src/core/localiser.hpp"
20#include "quest/src/comm/comm_config.hpp"
21#include "quest/src/comm/comm_routines.hpp"
22#include "quest/src/cpu/cpu_config.hpp"
23#include "quest/src/gpu/gpu_config.hpp"
24
25#include <string>
26#include <vector>
27
28using std::string;
29using std::vector;
30
31
32
33/*
34 * INTERNALLY EXPOSED FUNCTION
35 */
36
37
38Qureg qureg_populateNonHeapFields(int numQubits, int isDensMatr, int useDistrib, int useGpuAccel, int useMultithread) {
39
40 QuESTEnv env = getQuESTEnv();
41
42 // pre-prepare some struct fields (to avoid circular initialisation)
43 int logNumNodes = (useDistrib)?
44 logBase2(env.numNodes) : 0;
45 qindex logNumAmpsPerNode = (isDensMatr)?
46 (2*numQubits - logNumNodes) :
47 ( numQubits - logNumNodes);
48
49 // prepare output Qureg (avoiding C++20 designated initialiser)
50 Qureg out;
51
52 // bind deployment info
53 out.isMultithreaded = useMultithread;
54 out.isGpuAccelerated = useGpuAccel;
55 out.isDistributed = useDistrib;
56
57 // optionally bind distributed info, noting that in distributed environments,
58 // the non-distributed quregs are duplicated on each node and each believe
59 // they are the root node, with no other nodes existing; this is essential so
60 // that these quregs can agnostically use distributed routines which consult
61 // the rank, but it will interfere with naive root-only printing logic
62 out.rank = (useDistrib)? env.rank : 0;
63 out.numNodes = (useDistrib)? env.numNodes : 1;
64 out.logNumNodes = (useDistrib)? logBase2(env.numNodes) : 0; // duplicated for clarity
65
66 // bind dimensions
67 out.isDensityMatrix = isDensMatr;
68 out.numQubits = numQubits;
69 out.numAmps = (isDensMatr)? powerOf2(2*numQubits) : powerOf2(numQubits);
70 out.logNumAmps = (isDensMatr)? 2*numQubits : numQubits;
71
72 // bind dimensions per node (even if not distributed)
73 out.numAmpsPerNode = powerOf2(logNumAmpsPerNode);
74 out.logNumAmpsPerNode = logNumAmpsPerNode;
75 out.logNumColsPerNode = (isDensMatr)? numQubits - logNumNodes : 0; // used only by density matrices
76
77 // caller will allocate heap memory as necessary
78 out.cpuAmps = nullptr;
79 out.gpuAmps = nullptr;
80 out.cpuCommBuffer = nullptr;
81 out.gpuCommBuffer = nullptr;
82
83 return out;
84}
85
86
87
88/*
89 * PRIVATE INNER FUNCTIONS (C++)
90 */
91
92
93bool didAnyLocalAllocsFail(Qureg qureg) {
94
95 // CPU memory should always be allocated
96 if (! mem_isAllocated(qureg.cpuAmps))
97 return true;
98
99 // when distributed, the CPU communication buffer must be allocated
100 if (qureg.isDistributed && ! mem_isAllocated(qureg.cpuCommBuffer))
101 return true;
102
103 // when GPU-accelerated, the GPU memory should be allocated
104 if (qureg.isGpuAccelerated && ! mem_isAllocated(qureg.gpuAmps))
105 return true;
106
107 // when both distributed and GPU-accelerated, the GPU communication buffer must be allocated
108 if (qureg.isDistributed && qureg.isGpuAccelerated && ! mem_isAllocated(qureg.gpuCommBuffer))
109 return true;
110
111 // otherwise all pointers were non-NULL so no allocations failed
112 return false;
113}
114
115
116bool didAnyAllocsFailOnAnyNode(Qureg qureg) {
117
118 bool anyFail = didAnyLocalAllocsFail(qureg);
119 if (comm_isActive())
120 anyFail = comm_isTrueOnAllNodes(anyFail);
121
122 return anyFail;
123}
124
125
126void freeAllMemoryIfAnyAllocsFailed(Qureg qureg) {
127
128 // do nothing if everything allocated successfully between all nodes
129 if (!didAnyAllocsFailOnAnyNode(qureg))
130 return;
131
132 // otherwise, free everything that was successfully allocated (freeing nullptr is legal)
133 cpu_deallocArray(qureg.cpuAmps);
134 cpu_deallocArray(qureg.cpuCommBuffer);
135
136 // although we avoid calling GPU deallocation in non-GPU mode
137 if (qureg.isGpuAccelerated) {
138 gpu_deallocArray(qureg.gpuAmps);
139 gpu_deallocArray(qureg.gpuCommBuffer);
140 }
141}
142
143
144Qureg validateAndCreateCustomQureg(int numQubits, int isDensMatr, int useDistrib, int useGpuAccel, int useMultithread, const char* caller) {
145
146 validate_envIsInit(caller);
147 QuESTEnv env = getQuESTEnv();
148
149 // ensure deployment is compatible with environment, considering available hardware and their memory capacities
150 validate_newQuregParams(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread, env, caller);
151
152 // automatically overwrite distrib, GPU, and multithread fields which were left as modeflag::USE_AUTO
153 autodep_chooseQuregDeployment(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread, env);
154
155 Qureg qureg = qureg_populateNonHeapFields(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread);
156
157 // always allocate CPU memory
158 qureg.cpuAmps = cpu_allocNumaArray(qureg.numAmpsPerNode); // nullptr if failed
159
160 // conditionally allocate GPU memory and communication buffers (even if numNodes == 1).
161 // note that in distributed settings but where useDistrib=false, each node will have a
162 // full copy of the amplitudes, but will NOT have the communication buffers allocated.
163 qureg.gpuAmps = (useGpuAccel)? gpu_allocArray(qureg.numAmpsPerNode) : nullptr;
164 qureg.cpuCommBuffer = (useDistrib)? cpu_allocArray(qureg.numAmpsPerNode) : nullptr;
165 qureg.gpuCommBuffer = (useGpuAccel && useDistrib)? gpu_allocArray(qureg.numAmpsPerNode) : nullptr;
166
167 // if any of the above mallocs failed, below validation will memory leak; so free first (but don't set to nullptr)
168 freeAllMemoryIfAnyAllocsFailed(qureg);
169 validate_newQuregAllocs(qureg, __func__);
170
171 // TODO / DEBUG:
172 // TEMPORARILY FORBID HIP GPU ACCELERATION OF > 2^32 AMPS PER NODE,
173 // SINCE THIS WILL LEAD TO LATER KERNEL LAUNCH FAILURES; see issue #815
174 if (useGpuAccel && gpu_isHipCompiled() && qureg.numAmpsPerNode >= (1LL << 32))
175 error_quregTooLargeForHipGpuAccel();
176
177 // initialise state to |0> or |0><0|
178 initZeroState(qureg);
179
180 return qureg;
181}
182
183
184
185/*
186 * PRIVATE QUREG REPORTING INNER FUNCTIONS
187 */
188
189
190void printDeploymentInfo(Qureg qureg) {
191
192 print_table(
193 "deployment", {
194 {"isMpiEnabled", qureg.isDistributed},
195 {"isGpuEnabled", qureg.isGpuAccelerated},
196 {"isOmpEnabled", qureg.isMultithreaded},
197 });
198}
199
200void printDimensionInfo(Qureg qureg) {
201
202 using namespace printer_substrings;
203
204 // 2^N = M
205 string ampsStr;
206 ampsStr = bt + printer_toStr(qureg.numQubits * (qureg.isDensityMatrix? 2 : 1));
207 ampsStr += eq + printer_toStr(qureg.numAmps);
208
209 string colsStr = na;
210 if (qureg.isDensityMatrix)
211 colsStr = (
212 bt + printer_toStr(qureg.numQubits) +
213 eq + printer_toStr(powerOf2(qureg.numQubits)));
214
215 print_table(
216 "dimension", {
217 {"isDensMatr", printer_toStr(qureg.isDensityMatrix)},
218 {"numQubits", printer_toStr(qureg.numQubits)},
219 {"numCols", colsStr},
220 {"numAmps", ampsStr},
221 });
222}
223
224
225void printDistributionInfo(Qureg qureg) {
226
227 using namespace printer_substrings;
228
229 // not applicable when not distributed
230 string nodesStr = na;
231 string ampsStr = na;
232 string colsStr = na;
233
234 // 2^N = M per node
235 if (qureg.isDistributed) {
236 nodesStr = bt + printer_toStr(qureg.logNumNodes) + eq + printer_toStr(qureg.numNodes);
237 ampsStr = bt + printer_toStr(qureg.logNumAmpsPerNode) + eq + printer_toStr(qureg.numAmpsPerNode) + pn;
238 if (qureg.isDensityMatrix)
239 colsStr = bt + printer_toStr(qureg.logNumColsPerNode) + eq + printer_toStr(powerOf2(qureg.logNumColsPerNode)) + pn;
240 }
241
242 print_table(
243 "distribution", {
244 {"numNodes", nodesStr},
245 {"numCols", colsStr},
246 {"numAmps", ampsStr},
247 });
248}
249
250
251void printMemoryInfo(Qureg qureg) {
252
253 using namespace printer_substrings;
254
255 size_t localArrayMem = mem_getLocalQuregMemoryRequired(qureg.numAmpsPerNode);
256 string localMemStr = printer_getMemoryWithUnitStr(localArrayMem) + (qureg.isDistributed? pn : "");
257
258 // precondition: no reportable fields are at risk of overflow as a qindex
259 // type, EXCEPT aggregate total memory between distributed nodes (in bytes)
260 qindex globalTotalMem = mem_getTotalGlobalMemoryUsed(qureg);
261 string globalMemStr = (globalTotalMem == 0)? "overflowed" : printer_getMemoryWithUnitStr(globalTotalMem);
262
263 print_table(
264 "memory", {
265 {"cpuAmps", mem_isAllocated(qureg.cpuAmps)? localMemStr : na},
266 {"gpuAmps", mem_isAllocated(qureg.gpuAmps)? localMemStr : na},
267 {"cpuCommBuffer", mem_isAllocated(qureg.cpuCommBuffer)? localMemStr : na},
268 {"gpuCommBuffer", mem_isAllocated(qureg.gpuCommBuffer)? localMemStr : na},
269 {"globalTotal", globalMemStr},
270 });
271}
272
273
274
275/*
276 * PUBLIC C & C++ AGNOSTIC FUNCTIONS
277 */
278
279// enable invocation by both C and C++ binaries
280extern "C" {
281
282
283Qureg createCustomQureg(int numQubits, int isDensMatr, int useDistrib, int useGpuAccel, int useMultithread) {
284
285 return validateAndCreateCustomQureg(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread, __func__);
286}
287
288
289Qureg createQureg(int numQubits) {
290
291 int isDensMatr = 0;
292 int autoMode = modeflag::USE_AUTO;
293 return validateAndCreateCustomQureg(numQubits, isDensMatr, autoMode, autoMode, autoMode, __func__);
294}
295
296
297Qureg createDensityQureg(int numQubits) {
298
299 int isDensMatr = 1;
300 int autoMode = modeflag::USE_AUTO;
301 return validateAndCreateCustomQureg(numQubits, isDensMatr, autoMode, autoMode, autoMode, __func__);
302}
303
304
305Qureg createForcedQureg(int numQubits) {
306 validate_envIsInit(__func__);
307
308 QuESTEnv env = getQuESTEnv();
309
310 int isDensMatr = 0;
311 return validateAndCreateCustomQureg(numQubits, isDensMatr, env.isDistributed, env.isGpuAccelerated, env.isMultithreaded, __func__);
312}
313
314
316 validate_envIsInit(__func__);
317
318 QuESTEnv env = getQuESTEnv();
319
320 int isDensMatr = 1;
321 return validateAndCreateCustomQureg(numQubits, isDensMatr, env.isDistributed, env.isGpuAccelerated, env.isMultithreaded, __func__);
322}
323
324
326 validate_quregFields(qureg, __func__);
327
328 // create a new Qureg with identical fields, but zero'd memory
329 Qureg clone = validateAndCreateCustomQureg(
330 qureg.numQubits, qureg.isDensityMatrix, qureg.isDistributed,
331 qureg.isGpuAccelerated, qureg.isMultithreaded, __func__);
332
333 setQuregToClone(clone, qureg); // harmlessly re-validates
334
335 // if GPU-accelerated, clone's CPU amps are NOT updated
336 return clone;
337}
338
339
340void destroyQureg(Qureg qureg) {
341 validate_quregFields(qureg, __func__);
342
343 // free CPU memory
344 cpu_deallocNumaArray(qureg.cpuAmps, qureg.numAmpsPerNode);
345
346 // free CPU communication buffer
347 if (qureg.isDistributed)
348 cpu_deallocArray(qureg.cpuCommBuffer);
349
350 // free GPU memory
351 if (qureg.isGpuAccelerated)
352 gpu_deallocArray(qureg.gpuAmps);
353
354 // free GPU communication buffer
355 if (qureg.isGpuAccelerated && qureg.isDistributed)
356 gpu_deallocArray(qureg.gpuCommBuffer);
357
358 // cannot set free'd fields to nullptr because qureg
359 // wasn't passed-by-reference, and isn't returned.
360}
361
362
364 validate_quregFields(qureg, __func__);
365 validate_numReportedNewlinesAboveZero(__func__); // because trailing newline mandatory
366
367 /// @todo add function to write this output to file (useful for HPC debugging)
368
369 printer_sync();
370
371 print_label("Qureg");
372 printDeploymentInfo(qureg);
373 printDimensionInfo(qureg);
374 printDistributionInfo(qureg);
375 printMemoryInfo(qureg);
376
377 // exclude mandatory newline above
378 print_oneFewerNewlines();
379
380 printer_sync();
381}
382
383
384void reportQureg(Qureg qureg) {
385 validate_quregFields(qureg, __func__);
386 validate_numReportedNewlinesAboveZero(__func__); // because trailing newline mandatory
387
388 // account all local CPU memory (including buffer), neglecting GPU memory
389 // because it occupies distinct memory spaces, confusing accounting
390 size_t localMem = mem_getLocalQuregMemoryRequired(qureg.numAmpsPerNode);
391 if (qureg.isDistributed)
392 localMem *= 2; // include buffer. @todo will this ever overflow?!?!
393
394 // include struct size (expected negligibly tiny)
395 localMem += sizeof(qureg);
396
397 printer_sync();
398
399 print_header(qureg, localMem);
400 print_elems(qureg);
401
402 // exclude mandatory newline above
403 print_oneFewerNewlines();
404
405 printer_sync();
406}
407
408
410 validate_quregFields(qureg, __func__);
411
412 // permit this to be called even in non-GPU mode
413 if (qureg.isGpuAccelerated)
414 gpu_copyCpuToGpu(qureg); // syncs then overwrites all local GPU amps
415}
417 validate_quregFields(qureg, __func__);
418
419 // permit this to be called even in non-GPU mode
420 if (qureg.isGpuAccelerated)
421 gpu_copyGpuToCpu(qureg); // syncs then overwrites all local CPU amps
422}
423
424
425void syncSubQuregToGpu(Qureg qureg, qindex localStartInd, qindex numLocalAmps) {
426 validate_quregFields(qureg, __func__);
427 validate_localAmpIndices(qureg, localStartInd, numLocalAmps, __func__);
428
429 // the above validation communicates for node consensus in
430 // distributed settings, because params can differ per-node.
431 // note also this function accepts statevectors AND density
432 // matrices, because the latter does not need a bespoke
433 // (row,col) interface, because the user can only access/modify
434 // local density matrix amps via a flat index anyway!
435
436 // we permit this function to do nothing when not GPU-accelerated
437 if (!qureg.isGpuAccelerated)
438 return;
439
440 // otherwise, every node merely copies its local subset, which
441 // may differ per-node, in an embarrassingly parallel manner
442 gpu_copyCpuToGpu(&qureg.cpuAmps[localStartInd], &qureg.gpuAmps[localStartInd], numLocalAmps);
443}
444void syncSubQuregFromGpu(Qureg qureg, qindex localStartInd, qindex numLocalAmps) {
445 validate_quregFields(qureg, __func__);
446 validate_localAmpIndices(qureg, localStartInd, numLocalAmps, __func__);
447
448 // the above validation communicates for node consensus in
449 // distributed settings, because params can differ per-node.
450 // note also this function accepts statevectors AND density
451 // matrices, because the latter does not need a bespoke
452 // (row,col) interface, because the user can only access/modify
453 // local density matrix amps via a flat index anyway!
454
455 // we permit this function to do nothing when not GPU-accelerated
456 if (!qureg.isGpuAccelerated)
457 return;
458
459 // otherwise, every node merely copies its local subset, which
460 // may differ per-node, in an embarrassingly parallel manner
461 gpu_copyGpuToCpu(&qureg.gpuAmps[localStartInd], &qureg.cpuAmps[localStartInd], numLocalAmps);
462}
463
464
465void getQuregAmps(qcomp* outAmps, Qureg qureg, qindex startInd, qindex numAmps) {
466 validate_quregFields(qureg, __func__);
467 validate_quregIsStateVector(qureg, __func__);
468 validate_basisStateIndices(qureg, startInd, numAmps, __func__);
469
470 localiser_statevec_getAmps(outAmps, qureg, startInd, numAmps);
471}
472
473
474void getDensityQuregAmps(qcomp** outAmps, Qureg qureg, qindex startRow, qindex startCol, qindex numRows, qindex numCols) {
475 validate_quregFields(qureg, __func__);
476 validate_quregIsDensityMatrix(qureg, __func__);
477 validate_basisStateRowCols(qureg, startRow, startCol, numRows, numCols, __func__);
478
479 localiser_densmatr_getAmps(outAmps, qureg, startRow, startCol, numRows, numCols);
480}
481
482
483
484// end de-mangler
485}
486
487
488
489/*
490 * C++ ONLY FUNCTIONS
491 *
492 * which are not directly C-compatible because of limited
493 * interoperability of the qcomp type. See calculations.h
494 * for more info. We here define a C++-only signature (with
495 * name-mangling), and a C-friendly wrapper which passes by
496 * pointer; the C-friendly interface in wrappers.h which itself
497 * wrap this.
498 */
499
500
501qcomp getQuregAmp(Qureg qureg, qindex index) {
502 validate_quregFields(qureg, __func__);
503 validate_quregIsStateVector(qureg, __func__);
504 validate_basisStateIndex(qureg, index, __func__);
505
506 return localiser_statevec_getAmp(qureg, index);
507}
508extern "C" void _wrap_getQuregAmp(qcomp* out, Qureg qureg, qindex index) {
509
510 *out = getQuregAmp(qureg, index);
511}
512
513
514qcomp getDensityQuregAmp(Qureg qureg, qindex row, qindex column) {
515 validate_quregFields(qureg, __func__);
516 validate_quregIsDensityMatrix(qureg, __func__);
517 validate_basisStateRowCol(qureg, row, column, __func__);
518
519 qindex ind = util_getGlobalFlatIndex(qureg, row, column);
520 qcomp amp = localiser_statevec_getAmp(qureg, ind);
521 return amp;
522}
523extern "C" void _wrap_getDensityQuregAmp(qcomp* out, Qureg qureg, qindex row, qindex column) {
524
525 *out = getDensityQuregAmp(qureg, row, column);
526}
527
528
529
530/*
531 * C++ OVERLOADS
532 */
533
534
535vector<qcomp> getQuregAmps(Qureg qureg, qindex startInd, qindex numAmps) {
536
537 // allocate the output vector, and validate successful
538 vector<qcomp> out;
539 auto callback = [&]() { validate_tempListAllocSucceeded(false, numAmps, sizeof(qcomp), __func__); };
540 util_tryAllocVector(out, numAmps, callback);
541
542 // performs main validation
543 getQuregAmps(out.data(), qureg, startInd, numAmps);
544 return out;
545}
546
547
548vector<vector<qcomp>> getDensityQuregAmps(Qureg qureg, qindex startRow, qindex startCol, qindex numRows, qindex numCols) {
549
550 // allocate the output matrix, and validate successful
551 vector<vector<qcomp>> out;
552 qindex numElems = numRows * numCols; // never overflows (else Qureg alloc would fail)
553 auto callback1 = [&]() { validate_tempListAllocSucceeded(false, numElems, sizeof(qcomp), __func__); };
554 util_tryAllocMatrix(out, numRows, numCols, callback1);
555
556 // we must pass nested pointers to core C function, requiring another temp array, also validated
557 vector<qcomp*> ptrs;
558 auto callback2 = [&]() { validate_tempListAllocSucceeded(false, numRows, sizeof(qcomp*), __func__); };
559 util_tryAllocVector(ptrs, numRows, callback2);
560
561 // embed out pointers
562 for (qindex i=0; i<numRows; i++)
563 ptrs[i] = out[i].data();
564
565 // modify out through its ptrs
566 getDensityQuregAmps(ptrs.data(), qureg, startRow, startCol, numRows, numCols);
567 return out;
568}
QuESTEnv getQuESTEnv()
void setQuregToClone(Qureg outQureg, Qureg inQureg)
void initZeroState(Qureg qureg)
Qureg createDensityQureg(int numQubits)
Definition qureg.cpp:297
Qureg createForcedQureg(int numQubits)
Definition qureg.cpp:305
Qureg createForcedDensityQureg(int numQubits)
Definition qureg.cpp:315
Qureg createCloneQureg(Qureg qureg)
Definition qureg.cpp:325
Qureg createCustomQureg(int numQubits, int isDensMatr, int useDistrib, int useGpuAccel, int useMultithread)
Definition qureg.cpp:283
Qureg createQureg(int numQubits)
Definition qureg.cpp:289
void destroyQureg(Qureg qureg)
Definition qureg.cpp:340
qcomp getQuregAmp(Qureg qureg, qindex index)
Definition qureg.cpp:501
void getDensityQuregAmps(qcomp **outAmps, Qureg qureg, qindex startRow, qindex startCol, qindex numRows, qindex numCols)
Definition qureg.cpp:474
void getQuregAmps(qcomp *outAmps, Qureg qureg, qindex startInd, qindex numAmps)
Definition qureg.cpp:465
qcomp getDensityQuregAmp(Qureg qureg, qindex row, qindex column)
Definition qureg.cpp:514
void reportQureg(Qureg qureg)
Definition qureg.cpp:384
void reportQuregParams(Qureg qureg)
Definition qureg.cpp:363
void syncQuregFromGpu(Qureg qureg)
Definition qureg.cpp:416
void syncSubQuregToGpu(Qureg qureg, qindex localStartInd, qindex numLocalAmps)
Definition qureg.cpp:425
void syncSubQuregFromGpu(Qureg qureg, qindex localStartInd, qindex numLocalAmps)
Definition qureg.cpp:444
void syncQuregToGpu(Qureg qureg)
Definition qureg.cpp:409
Definition qureg.h:49