The Quantum Exact Simulation Toolkit v4.3.0
Loading...
Searching...
No Matches
experimental.cpp
1/** @file
2 * Experimental functions which are liable to
3 * API breaks within QuEST minor version releases.
4 * Some optional functions require compiling this
5 * file against MPI, despite being outside of /comm/,
6 * and so require opt-in macros (QUEST_COMPILE_SUBCOMM)
7 *
8 * @author Oliver Brown (custom QuESTEnv)
9 * @author Ashmit JaiSarita Gupta (checkpointing)
10 * @author Tyson Jones (structure, validation)
11 */
12
13#include "quest/include/config.h"
14#include "quest/include/environment.h"
15#include "quest/include/qureg.h"
16#include "quest/include/modes.h"
17
18#include "quest/src/core/validation.hpp"
19#include "quest/src/comm/comm_config.hpp"
20#include "quest/src/gpu/gpu_config.hpp"
21
22#include <string>
23
24#if QUEST_COMPILE_SUBCOMM && ! QUEST_COMPILE_MPI
25 #error "Macro QUEST_COMPILE_SUBCOMM was true, but QUEST_COMPILE_MPI was illegally false."
26#endif
27
28#if QUEST_COMPILE_SUBCOMM
29 #include <mpi.h>
30#endif
31
32#if QUEST_COMPILE_ADIOS2
33 #include <adios2.h>
34
35 #if QUEST_COMPILE_MPI
36 #include <mpi.h>
37 #endif
38#endif
39
40
41
42/*
43 * EXTERNAL FUNCTIONS
44 *
45 * which we here regretfully 'extern' because we are either
46 * unsure which header should expose them, or because they
47 * contain deployment-specific types (like MPI_Comm) which
48 * we do not wish to expose within internal headers
49 */
50
51
52extern void validateAndInitCustomQuESTEnv(
53 int useDistrib, bool userOwnsMpi, int useGpuAccel, int useMultithread, const char* caller);
54
55
56extern Qureg validateAndCreateCustomQureg(
57 int numQubits, int isDensMatr, int useDistrib, int useGpuAccel, int useMultithread, const char* caller);
58
59
60#if QUEST_COMPILE_SUBCOMM // hide MPI_Comm
61 extern bool comm_setMpiComm(MPI_Comm newComm, bool userOwnsMpi);
62#endif
63
64
65#if (QUEST_COMPILE_ADIOS2 && QUEST_COMPILE_MPI) // hide MPI_Comm
66 extern MPI_Comm comm_getMpiComm();
67#endif
68
69
70
71/*
72 * INTERNAL FUNCTIONS
73 */
74
75
76#if QUEST_COMPILE_ADIOS2
77auto createAdios(bool useMpi) {
78
79 // suppress unused warning when MPI not compiled (implies useMpi=false)
80 (void) useMpi;
81
82 // When the Qureg is distributed, ADIOS2 must be given QuEST's communicator so that each
83 // node writes/reads its own slice of the shared file
84 #if QUEST_COMPILE_MPI
85 return useMpi?
86 adios2::ADIOS(comm_getMpiComm()) :
87 adios2::ADIOS();
88 #else
89 return adios2::ADIOS(); // implies useMpi=0
90 #endif
91
92 // caller need not call destructor; ADIOS2 uses RAII
93}
94#endif
95
96
97
98/*
99 * C API FUNCTIONS
100 */
101
102
103// enable invocation by both C and C++ binaries
104extern "C" {
105
106
107void initCustomMpiQuESTEnv(int useDistrib, bool userOwnsMpi, int useGpuAccel, int useMultithread) {
108 validateAndInitCustomQuESTEnv(useDistrib, userOwnsMpi, useGpuAccel, useMultithread, __func__);
109}
110
111
112#if QUEST_COMPILE_SUBCOMM // hide MPI_Comm
113void initCustomMpiCommQuESTEnv(MPI_Comm userQuestComm, int useGpuAccel, int useMultithread) {
114
115 // useDistrib and userOwnsMpi are implied by the user of this initialiser
116 const int useDistrib = 1;
117 const bool userOwnsMpi = true;
118
119 // pre-validate that we are able to set the MPI communicator
120 validate_mpiInitStatus(useDistrib, userOwnsMpi, __func__);
121 validate_mpiSubCommIsNonNull(userQuestComm != MPI_COMM_NULL, __func__);
122
123 // avoid re-setting the MPI comm (to avoid an internal error), which happens
124 // if a user illegally re-calls this function, which will be subsequently
125 // caught by the validation in validateAndInitCustomQuESTEnv() below
126 if (!comm_isActive()) {
127 bool success = comm_setMpiComm(userQuestComm, userOwnsMpi);
128 validate_mpiSubCommSetSucceeded(success, __func__);
129 }
130
131 // perform remaining validation (some is harmlessly repeated) and init QuEST env
132 validateAndInitCustomQuESTEnv(useDistrib, userOwnsMpi, useGpuAccel, useMultithread, __func__);
133}
134#endif // QUEST_COMPILE_SUBCOMM
135
136
138 validate_envIsInit(__func__);
139
140 return gpu_getNumThreadsPerBlock();
141}
142
143
145 validate_envIsInit(__func__);
146
147 // validation messages and queries depend upon GPU usage
148 bool gpuIsActive = getQuESTEnv().isGpuAccelerated;
149 validate_numGpuThreadsPerBlock(numTPB, gpuIsActive, __func__);
150
151 gpu_setNumThreadsPerBlock(numTPB);
152}
153
154
155void saveQuregToFile(Qureg qureg, const char* fn) {
156 validate_adios2IsCompiled(__func__);
157 validate_quregFields(qureg, __func__);
158
159 (void) fn; // suppress unused warning
160
161#if QUEST_COMPILE_ADIOS2
162
163 // When the QuEST env is distributed, but the given Qureg is not (and is instead
164 // duplicated upon every node), we should permit only a single process (the root)
165 // to use ADIOS2 to write to the (assumably, shared) filesystem. Note that non-root
166 // nodes must not exit; they need to participate in validation syncs
167 bool shouldSkipAdios = (! qureg.isDistributed) && (comm_getRank() > ROOT_RANK);
168
169 // pedantic but safe - don't let ADIOS2 start reading amps prematurely
170 if (qureg.isDistributed)
171 comm_sync();
172
173 // TODO:
174 // We can optimise in GPU settings by giving ADIOS2 the device memory
175 // pointers; but for now, we simply stage into CPU memory first
176 if (qureg.isGpuAccelerated)
177 gpu_copyGpuToCpu(qureg);
178
179 // gratuitously re-create ADIOS2 at every call, for simplicity (occluded by IO)
180 adios2::ADIOS adios = createAdios(qureg.isDistributed);
181 adios2::IO io = adios.DeclareIO("QuESTQuregSave");
182
183 // use BP5 specifically to avoid non-root-hangs upon rank exceptions
184 io.SetEngine("BP5");
185
186 // attempt to open the file
187 adios2::Engine engine; // default ctor
188 bool success = false;
189 try {
190 if (!shouldSkipAdios)
191 engine = io.Open(fn, adios2::Mode::Write);
192 success = true;
193 } catch (...) {}
194 validate_adiosCanOpenFileOnAllNodes(success, fn, __func__);
195
196 // global single-value metadata; we deliberately record only the dimension
197 // and precision, never incidental deployment fields (the loader chooses its
198 // own deployment) nor derivable fields (like numAmps)
199 adios2::Variable<int> vNumQubits = io.DefineVariable<int>("numQubits");
200 adios2::Variable<int> vNumNodes = io.DefineVariable<int>("numNodes");
201 adios2::Variable<int> vIsDensMatr = io.DefineVariable<int>("isDensityMatrix");
202 adios2::Variable<size_t> vQrealBytes = io.DefineVariable<size_t>("qrealBytes"); // also encodes precision
203
204 // amplitudes are stored as interleaved (real, imag) reals to stay agnostic
205 // to precision and to ADIOS2's complex-type support; each node writes only
206 // its local slice into the global array, avoiding excessive memory use
207 // (these scalars are guaranteed not to overflow by createQureg validation)
208 qindex globalReals = 2 * qureg.numAmps;
209 qindex localReals = 2 * qureg.numAmpsPerNode;
210 qindex startReal = localReals * qureg.rank;
211 adios2::Variable<qreal> vAmpComponents = io.DefineVariable<qreal>(
212 "ampComponents",
213 { (size_t) globalReals },
214 { (size_t) startReal },
215 { (size_t) localReals });
216
217 // attempt to write to file
218 success = false;
219 try {
220 if (!shouldSkipAdios) {
221 engine.Put(vNumQubits, qureg.numQubits);
222 engine.Put(vNumNodes, qureg.numNodes);
223 engine.Put(vIsDensMatr, qureg.isDensityMatrix);
224 engine.Put(vQrealBytes, sizeof(qreal));
225 engine.Put(vAmpComponents, reinterpret_cast<qreal*>(qureg.cpuAmps));
226 engine.Close();
227 }
228 success = true;
229 } catch (...) {}
230 validate_adiosCanWriteToFileOnAllNodes(success, fn, __func__);
231
232 // prevent any process from continuing until ADIOS2 is fully finished
233 if (qureg.isDistributed)
234 comm_sync();
235
236#endif
237}
238
239
240Qureg createQuregFromFile(const char* fn) {
241 validate_adios2IsCompiled(__func__);
242
243#if QUEST_COMPILE_ADIOS2
244
245 // pedantic but safe - don't let ADIOS2 start reading while other processes are working
246 if (comm_isActive())
247 comm_sync();
248
249 // make ADIOS2 MPI-aware even when the subsequently-loaded Qureg is
250 // auto-deployed to be non-distributed; every process will safely
251 // parse the file and independently update its Qureg copy
252 bool giveAdiosMpi = comm_isActive();
253
254 // gratuitously re-create ADIOS2 at every call, for simplicity (occluded by IO)
255 adios2::ADIOS adios = createAdios(giveAdiosMpi);
256 adios2::IO io = adios.DeclareIO("QuESTQuregLoad");
257
258 // use BP5 specifically to avoid non-root-hangs upon rank exceptions
259 io.SetEngine("BP5");
260
261 // attempt to open the file, and prepare to parse
262 adios2::Engine engine; // default ctor
263 bool success = false;
264 try {
265 engine = io.Open(fn, adios2::Mode::ReadRandomAccess);
266 success = true;
267 } catch (...) {}
268 validate_adiosCanOpenFileOnAllNodes(success, fn, __func__);
269
270 // check that the file contains the expected variables
271 auto vNumQubits = io.InquireVariable<int>("numQubits");
272 auto vNumNodes = io.InquireVariable<int>("numNodes");
273 auto vIsDensMatr = io.InquireVariable<int>("isDensityMatrix");
274 auto vQrealBytes = io.InquireVariable<size_t>("qrealBytes");
275 auto vAmpComponents = io.InquireVariable<qreal>("ampComponents");
276 bool areAllVarsPresent = vNumQubits && vNumNodes && vIsDensMatr && vQrealBytes && vAmpComponents;
277 validate_adiosFileContainsFieldsOnAllNodes(areAllVarsPresent, __func__);
278
279 // read dimension + precision metadata first, so we can size the new Qureg
280 int numQubits = 0;
281 int numNodes = 0;
282 int isDensMatr = 0;
283 size_t fileQrealBytes = 0;
284 success = false;
285 try {
286 engine.Get(vNumQubits, numQubits);
287 engine.Get(vNumNodes, numNodes);
288 engine.Get(vIsDensMatr, isDensMatr);
289 engine.Get(vQrealBytes, fileQrealBytes);
290 engine.PerformGets();
291 success = true;
292 } catch (...) {}
293 validate_adiosCanReadFileOnAllNodes(success, fn, __func__);
294
295 // check the amps are of the expected precision, and so are parsable
296 validate_newQuregFileMatchesPrecision(fileQrealBytes, __func__);
297
298 // attempt to create a matching-dimension Qureg with automatically chosen deployments
299 Qureg qureg = validateAndCreateCustomQureg(numQubits, isDensMatr,
300 modeflag::USE_AUTO, modeflag::USE_AUTO, modeflag::USE_AUTO, __func__);
301
302 // auto-distribution MUST match checkpointed distribution (pre-free to avoid leak)
303 if (qureg.numNodes != numNodes)
304 destroyQureg(qureg);
305 validate_newQuregNumNodesMatchesSavedFile(numNodes, qureg.numNodes, comm_getNumNodes(), numQubits, isDensMatr, __func__);
306
307 // read only this node's slice of the global amplitude array into its buffer
308 // (guaranteed not to overflow by above validateAndCreateCustomQureg validation)
309 qindex localReals = 2 * qureg.numAmpsPerNode;
310 qindex startReal = 2 * ((qindex) qureg.rank) * qureg.numAmpsPerNode;
311 vAmpComponents.SetSelection({ { (size_t) startReal }, { (size_t) localReals } });
312 success = false;
313 try {
314 engine.Get(vAmpComponents, reinterpret_cast<qreal*>(qureg.cpuAmps)); // immediate; PerformGets redundant
315 success = true;
316 } catch (...) {}
317 validate_adiosCanReadFileOnAllNodes(success, fn, __func__);
318
319 // complete ADIOS2 work
320 success = false;
321 try {
322 engine.Close();
323 success = true;
324 } catch (...) {}
325 validate_adiosCanReadFileOnAllNodes(success, fn, __func__);
326
327 // propagate the restored CPU amplitudes to the GPU, if deployed
328 if (qureg.isGpuAccelerated)
329 gpu_copyCpuToGpu(qureg);
330
331 return qureg;
332#else
333 // unreachable: the validation above always throws in non-checkpointing builds
334 return Qureg{};
335#endif
336}
337
338
339// end de-mangler
340}
341
342
343/*
344 * C++ API FUNCTIONS
345 */
346
347void saveQuregToFile(Qureg qureg, std::string fn) {
348
349 saveQuregToFile(qureg, fn.c_str());
350}
351
352Qureg createQuregFromFile(std::string fn) {
353
354 return createQuregFromFile(fn.c_str());
355}
QuESTEnv getQuESTEnv()
void initCustomMpiQuESTEnv(int useDistrib, bool userOwnsMpi, int useGpuAccel, int useMultithread)
void initCustomMpiCommQuESTEnv(MPI_Comm userQuestComm, int useGpuAccel, int useMultithread)
Qureg createQuregFromFile(const char *fn)
int getQuESTNumGpuThreadsPerBlock()
void setQuESTNumGpuThreadsPerBlock(int numTPB)
void saveQuregToFile(Qureg qureg, const char *fn)
void destroyQureg(Qureg qureg)
Definition qureg.cpp:340
Definition qureg.h:49