xref: /libCEED/interface/ceed.c (revision b559d91ba7f8b07d9f0c29b369435665983b29e7) !
1 // Copyright (c) 2017-2022, Lawrence Livermore National Security, LLC and other CEED contributors.
2 // All Rights Reserved. See the top-level LICENSE and NOTICE files for details.
3 //
4 // SPDX-License-Identifier: BSD-2-Clause
5 //
6 // This file is part of CEED:  http://github.com/ceed
7 
8 #define _POSIX_C_SOURCE 200112
9 #include <ceed-impl.h>
10 #include <ceed/backend.h>
11 #include <ceed/ceed.h>
12 #include <limits.h>
13 #include <stdarg.h>
14 #include <stddef.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 
19 /// @cond DOXYGEN_SKIP
20 static CeedRequest ceed_request_immediate;
21 static CeedRequest ceed_request_ordered;
22 
23 static struct {
24   char prefix[CEED_MAX_RESOURCE_LEN];
25   int (*init)(const char *resource, Ceed f);
26   unsigned int priority;
27 } backends[32];
28 static size_t num_backends;
29 
30 #define CEED_FTABLE_ENTRY(class, method)                     \
31   {                                                          \
32 #class #method, offsetof(struct class##_private, method) \
33   }
34 /// @endcond
35 
36 /// @file
37 /// Implementation of core components of Ceed library
38 
39 /// @addtogroup CeedUser
40 /// @{
41 
42 /**
43   @brief Request immediate completion
44 
45   This predefined constant is passed as the \ref CeedRequest argument to interfaces when the caller wishes for the operation to be performed
46 immediately. The code
47 
48   @code
49     CeedOperatorApply(op, ..., CEED_REQUEST_IMMEDIATE);
50   @endcode
51 
52   is semantically equivalent to
53 
54   @code
55     CeedRequest request;
56     CeedOperatorApply(op, ..., &request);
57     CeedRequestWait(&request);
58   @endcode
59 
60   @sa CEED_REQUEST_ORDERED
61 **/
62 CeedRequest *const CEED_REQUEST_IMMEDIATE = &ceed_request_immediate;
63 
64 /**
65   @brief Request ordered completion
66 
67   This predefined constant is passed as the \ref CeedRequest argument to interfaces when the caller wishes for the operation to be completed in the
68   order that it is submitted to the device. It is typically used in a construct such as
69 
70   @code
71     CeedRequest request;
72     CeedOperatorApply(op1, ..., CEED_REQUEST_ORDERED);
73     CeedOperatorApply(op2, ..., &request);
74     // other optional work
75     CeedRequestWait(&request);
76   @endcode
77 
78   which allows the sequence to complete asynchronously but does not start `op2` until `op1` has completed.
79 
80   @todo The current implementation is overly strict, offering equivalent semantics to @ref CEED_REQUEST_IMMEDIATE.
81 
82   @sa CEED_REQUEST_IMMEDIATE
83  */
84 CeedRequest *const CEED_REQUEST_ORDERED = &ceed_request_ordered;
85 
86 /**
87   @brief Wait for a CeedRequest to complete.
88 
89   Calling CeedRequestWait on a NULL request is a no-op.
90 
91   @param req Address of CeedRequest to wait for; zeroed on completion.
92 
93   @return An error code: 0 - success, otherwise - failure
94 
95   @ref User
96 **/
97 int CeedRequestWait(CeedRequest *req) {
98   if (!*req) return CEED_ERROR_SUCCESS;
99   return CeedError(NULL, CEED_ERROR_UNSUPPORTED, "CeedRequestWait not implemented");
100 }
101 
102 /// @}
103 
104 /// ----------------------------------------------------------------------------
105 /// Ceed Library Internal Functions
106 /// ----------------------------------------------------------------------------
107 /// @addtogroup CeedDeveloper
108 /// @{
109 
110 /**
111   @brief Register a Ceed backend internally.
112            Note: Backends should call `CeedRegister` instead.
113 
114   @param[in] prefix    Prefix of resources for this backend to respond to.
115                          For example, the reference backend responds to "/cpu/self".
116   @param[in] init      Initialization function called by CeedInit() when the backend is selected to drive the requested resource.
117   @param[in] priority  Integer priority.
118                          Lower values are preferred in case the resource requested by CeedInit() has non-unique best prefix match.
119 
120   @return An error code: 0 - success, otherwise - failure
121 
122   @ref Developer
123 **/
124 int CeedRegisterImpl(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
125   if (num_backends >= sizeof(backends) / sizeof(backends[0]))
126     // LCOV_EXCL_START
127     return CeedError(NULL, CEED_ERROR_MAJOR, "Too many backends");
128   // LCOV_EXCL_STOP
129 
130   strncpy(backends[num_backends].prefix, prefix, CEED_MAX_RESOURCE_LEN);
131   backends[num_backends].prefix[CEED_MAX_RESOURCE_LEN - 1] = 0;
132   backends[num_backends].init                              = init;
133   backends[num_backends].priority                          = priority;
134   num_backends++;
135   return CEED_ERROR_SUCCESS;
136 }
137 
138 /// @}
139 
140 /// ----------------------------------------------------------------------------
141 /// Ceed Backend API
142 /// ----------------------------------------------------------------------------
143 /// @addtogroup CeedBackend
144 /// @{
145 
146 /**
147   @brief Return value of CEED_DEBUG environment variable
148 
149   @param[in] ceed Ceed context
150 
151   @return boolean value: true  - debugging mode enabled
152                          false - debugging mode disabled
153 
154   @ref Backend
155 **/
156 // LCOV_EXCL_START
157 bool CeedDebugFlag(const Ceed ceed) { return ceed->is_debug; }
158 // LCOV_EXCL_STOP
159 
160 /**
161   @brief Return value of CEED_DEBUG environment variable
162 
163   @return boolean value: true  - debugging mode enabled
164                          false - debugging mode disabled
165 
166   @ref Backend
167 **/
168 // LCOV_EXCL_START
169 bool CeedDebugFlagEnv(void) { return !!getenv("CEED_DEBUG") || !!getenv("DEBUG") || !!getenv("DBG"); }
170 // LCOV_EXCL_STOP
171 
172 /**
173   @brief Print debugging information in color
174 
175   @param color   Color to print
176   @param format  Printing format
177 
178   @ref Backend
179 **/
180 // LCOV_EXCL_START
181 void CeedDebugImpl256(const unsigned char color, const char *format, ...) {
182   va_list args;
183   va_start(args, format);
184   fflush(stdout);
185   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[38;5;%dm", color);
186   vfprintf(stdout, format, args);
187   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[m");
188   fprintf(stdout, "\n");
189   fflush(stdout);
190   va_end(args);
191 }
192 // LCOV_EXCL_STOP
193 
194 /**
195   @brief Allocate an array on the host; use CeedMalloc()
196 
197   Memory usage can be tracked by the library.
198   This ensures sufficient alignment for vectorization and should be used for large allocations.
199 
200   @param[in]  n    Number of units to allocate
201   @param[in]  unit Size of each unit
202   @param[out] p    Address of pointer to hold the result.
203 
204   @return An error code: 0 - success, otherwise - failure
205 
206   @sa CeedFree()
207 
208   @ref Backend
209 **/
210 int CeedMallocArray(size_t n, size_t unit, void *p) {
211   int ierr = posix_memalign((void **)p, CEED_ALIGN, n * unit);
212   if (ierr) {
213     // LCOV_EXCL_START
214     return CeedError(NULL, CEED_ERROR_MAJOR, "posix_memalign failed to allocate %zd members of size %zd\n", n, unit);
215     // LCOV_EXCL_STOP
216   }
217   return CEED_ERROR_SUCCESS;
218 }
219 
220 /**
221   @brief Allocate a cleared (zeroed) array on the host; use CeedCalloc()
222 
223   Memory usage can be tracked by the library.
224 
225   @param[in]  n    Number of units to allocate
226   @param[in]  unit Size of each unit
227   @param[out] p    Address of pointer to hold the result.
228 
229   @return An error code: 0 - success, otherwise - failure
230 
231   @sa CeedFree()
232 
233   @ref Backend
234 **/
235 int CeedCallocArray(size_t n, size_t unit, void *p) {
236   *(void **)p = calloc(n, unit);
237   if (n && unit && !*(void **)p) {
238     // LCOV_EXCL_START
239     return CeedError(NULL, CEED_ERROR_MAJOR, "calloc failed to allocate %zd members of size %zd\n", n, unit);
240     // LCOV_EXCL_STOP
241   }
242   return CEED_ERROR_SUCCESS;
243 }
244 
245 /**
246   @brief Reallocate an array on the host; use CeedRealloc()
247 
248   Memory usage can be tracked by the library.
249 
250   @param[in]  n    Number of units to allocate
251   @param[in]  unit Size of each unit
252   @param[out] p    Address of pointer to hold the result.
253 
254   @return An error code: 0 - success, otherwise - failure
255 
256   @sa CeedFree()
257 
258   @ref Backend
259 **/
260 int CeedReallocArray(size_t n, size_t unit, void *p) {
261   *(void **)p = realloc(*(void **)p, n * unit);
262   if (n && unit && !*(void **)p) {
263     // LCOV_EXCL_START
264     return CeedError(NULL, CEED_ERROR_MAJOR, "realloc failed to allocate %zd members of size %zd\n", n, unit);
265     // LCOV_EXCL_STOP
266   }
267   return CEED_ERROR_SUCCESS;
268 }
269 
270 /**
271   @brief Allocate a cleared string buffer on the host
272 
273   Memory usage can be tracked by the library.
274 
275   @param[in]  source Pointer to string to be copied
276   @param[out] copy   Pointer to variable to hold newly allocated string copy
277 
278   @return An error code: 0 - success, otherwise - failure
279 
280   @sa CeedFree()
281 
282   @ref Backend
283 **/
284 int CeedStringAllocCopy(const char *source, char **copy) {
285   size_t len = strlen(source);
286   CeedCall(CeedCalloc(len + 1, copy));
287   memcpy(*copy, source, len);
288   return CEED_ERROR_SUCCESS;
289 }
290 
291 /** Free memory allocated using CeedMalloc() or CeedCalloc()
292 
293   @param[in,out] p  address of pointer to memory.
294                       This argument is of type void* to avoid needing a cast, but is the address of the pointer (which is zeroed) rather than the
295 pointer.
296 **/
297 int CeedFree(void *p) {
298   free(*(void **)p);
299   *(void **)p = NULL;
300   return CEED_ERROR_SUCCESS;
301 }
302 
303 /**
304   @brief Register a Ceed backend
305 
306   @param[in] prefix   Prefix of resources for this backend to respond to.
307                         For example, the reference backend responds to "/cpu/self".
308   @param[in] init     Initialization function called by CeedInit() when the backend is selected to drive the requested resource.
309   @param[in] priority Integer priority.
310                         Lower values are preferred in case the resource requested by CeedInit() has non-unique best prefix match.
311 
312   @return An error code: 0 - success, otherwise - failure
313 
314   @ref Backend
315 **/
316 int CeedRegister(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
317   CeedDebugEnv("Backend Register: %s", prefix);
318   CeedRegisterImpl(prefix, init, priority);
319   return CEED_ERROR_SUCCESS;
320 }
321 
322 /**
323   @brief Return debugging status flag
324 
325   @param[in]  ceed     Ceed context to get debugging flag
326   @param[out] is_debug Variable to store debugging flag
327 
328   @return An error code: 0 - success, otherwise - failure
329 
330   @ref Backend
331 **/
332 int CeedIsDebug(Ceed ceed, bool *is_debug) {
333   *is_debug = ceed->is_debug;
334   return CEED_ERROR_SUCCESS;
335 }
336 
337 /**
338   @brief Retrieve a parent Ceed context
339 
340   @param[in]  ceed   Ceed context to retrieve parent of
341   @param[out] parent Address to save the parent to
342 
343   @return An error code: 0 - success, otherwise - failure
344 
345   @ref Backend
346 **/
347 int CeedGetParent(Ceed ceed, Ceed *parent) {
348   if (ceed->parent) {
349     CeedCall(CeedGetParent(ceed->parent, parent));
350     return CEED_ERROR_SUCCESS;
351   }
352   *parent = ceed;
353   return CEED_ERROR_SUCCESS;
354 }
355 
356 /**
357   @brief Retrieve a delegate Ceed context
358 
359   @param[in]  ceed     Ceed context to retrieve delegate of
360   @param[out] delegate Address to save the delegate to
361 
362   @return An error code: 0 - success, otherwise - failure
363 
364   @ref Backend
365 **/
366 int CeedGetDelegate(Ceed ceed, Ceed *delegate) {
367   *delegate = ceed->delegate;
368   return CEED_ERROR_SUCCESS;
369 }
370 
371 /**
372   @brief Set a delegate Ceed context
373 
374   This function allows a Ceed context to set a delegate Ceed context.
375     All backend implementations default to the delegate Ceed context, unless overridden.
376 
377   @param[in]  ceed     Ceed context to set delegate of
378   @param[out] delegate Address to set the delegate to
379 
380   @return An error code: 0 - success, otherwise - failure
381 
382   @ref Backend
383 **/
384 int CeedSetDelegate(Ceed ceed, Ceed delegate) {
385   ceed->delegate   = delegate;
386   delegate->parent = ceed;
387   return CEED_ERROR_SUCCESS;
388 }
389 
390 /**
391   @brief Retrieve a delegate Ceed context for a specific object type
392 
393   @param[in]  ceed     Ceed context to retrieve delegate of
394   @param[out] delegate Address to save the delegate to
395   @param[in]  obj_name Name of the object type to retrieve delegate for
396 
397   @return An error code: 0 - success, otherwise - failure
398 
399   @ref Backend
400 **/
401 int CeedGetObjectDelegate(Ceed ceed, Ceed *delegate, const char *obj_name) {
402   // Check for object delegate
403   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) {
404     if (!strcmp(obj_name, ceed->obj_delegates->obj_name)) {
405       *delegate = ceed->obj_delegates->delegate;
406       return CEED_ERROR_SUCCESS;
407     }
408   }
409 
410   // Use default delegate if no object delegate
411   CeedCall(CeedGetDelegate(ceed, delegate));
412   return CEED_ERROR_SUCCESS;
413 }
414 
415 /**
416   @brief Set a delegate Ceed context for a specific object type
417 
418   This function allows a Ceed context to set a delegate Ceed context for a given type of Ceed object.
419     All backend implementations default to the delegate Ceed context for this object.
420     For example, CeedSetObjectDelegate(ceed, refceed, "Basis") uses refceed implementations for all CeedBasis backend functions.
421 
422   @param[in,out] ceed     Ceed context to set delegate of
423   @param[out]    delegate Address to set the delegate to
424   @param[in]     obj_name Name of the object type to set delegate for
425 
426   @return An error code: 0 - success, otherwise - failure
427 
428   @ref Backend
429 **/
430 int CeedSetObjectDelegate(Ceed ceed, Ceed delegate, const char *obj_name) {
431   CeedInt count = ceed->obj_delegate_count;
432 
433   // Malloc or Realloc
434   if (count) {
435     CeedCall(CeedRealloc(count + 1, &ceed->obj_delegates));
436   } else {
437     CeedCall(CeedCalloc(1, &ceed->obj_delegates));
438   }
439   ceed->obj_delegate_count++;
440 
441   // Set object delegate
442   ceed->obj_delegates[count].delegate = delegate;
443   CeedCall(CeedStringAllocCopy(obj_name, &ceed->obj_delegates[count].obj_name));
444 
445   // Set delegate parent
446   delegate->parent = ceed;
447   return CEED_ERROR_SUCCESS;
448 }
449 
450 /**
451   @brief Get the fallback resource for CeedOperators
452 
453   @param[in]  ceed     Ceed context
454   @param[out] resource Variable to store fallback resource
455 
456   @return An error code: 0 - success, otherwise - failure
457 
458   @ref Backend
459 **/
460 
461 int CeedGetOperatorFallbackResource(Ceed ceed, const char **resource) {
462   *resource = (const char *)ceed->op_fallback_resource;
463   return CEED_ERROR_SUCCESS;
464 }
465 
466 /**
467   @brief Get the fallback Ceed for CeedOperators
468 
469   @param[in]  ceed          Ceed context
470   @param[out] fallback_ceed Variable to store fallback Ceed
471 
472   @return An error code: 0 - success, otherwise - failure
473 
474   @ref Backend
475 **/
476 
477 int CeedGetOperatorFallbackCeed(Ceed ceed, Ceed *fallback_ceed) {
478   if (ceed->has_valid_op_fallback_resource) {
479     CeedDebug256(ceed, 1, "---------- CeedOperator Fallback ----------\n");
480     CeedDebug(ceed, "Getting fallback from %s to %s\n", ceed->resource, ceed->op_fallback_resource);
481   }
482 
483   // Create fallback Ceed if uninitalized
484   if (!ceed->op_fallback_ceed && ceed->has_valid_op_fallback_resource) {
485     CeedDebug(ceed, "Creating fallback Ceed");
486 
487     Ceed        fallback_ceed;
488     const char *fallback_resource;
489 
490     CeedCall(CeedGetOperatorFallbackResource(ceed, &fallback_resource));
491     CeedCall(CeedInit(fallback_resource, &fallback_ceed));
492     fallback_ceed->op_fallback_parent = ceed;
493     fallback_ceed->Error              = ceed->Error;
494     ceed->op_fallback_ceed            = fallback_ceed;
495   }
496   *fallback_ceed = ceed->op_fallback_ceed;
497 
498   return CEED_ERROR_SUCCESS;
499 }
500 
501 /**
502   @brief Set the fallback resource for CeedOperators.
503            The current resource, if any, is freed by calling this function.
504            This string is freed upon the destruction of the Ceed context.
505 
506   @param[in,out] ceed     Ceed context
507   @param[in]     resource Fallback resource to set
508 
509   @return An error code: 0 - success, otherwise - failure
510 
511   @ref Backend
512 **/
513 
514 int CeedSetOperatorFallbackResource(Ceed ceed, const char *resource) {
515   // Free old
516   CeedCall(CeedFree(&ceed->op_fallback_resource));
517 
518   // Set new
519   CeedCall(CeedStringAllocCopy(resource, (char **)&ceed->op_fallback_resource));
520 
521   // Check validity
522   ceed->has_valid_op_fallback_resource = ceed->op_fallback_resource && ceed->resource && strcmp(ceed->op_fallback_resource, ceed->resource);
523 
524   return CEED_ERROR_SUCCESS;
525 }
526 
527 /**
528   @brief Get the parent Ceed context associated with a fallback Ceed context for a CeedOperator
529 
530   @param[in]  ceed   Ceed context
531   @param[out] parent Variable to store parent Ceed context
532 
533   @return An error code: 0 - success, otherwise - failure
534 
535   @ref Backend
536 **/
537 
538 int CeedGetOperatorFallbackParentCeed(Ceed ceed, Ceed *parent) {
539   *parent = ceed->op_fallback_parent;
540   return CEED_ERROR_SUCCESS;
541 }
542 
543 /**
544   @brief Flag Ceed context as deterministic
545 
546   @param[in]  ceed             Ceed to flag as deterministic
547   @param[out] is_deterministic Deterministic status to set
548 
549   @return An error code: 0 - success, otherwise - failure
550 
551   @ref Backend
552 **/
553 
554 int CeedSetDeterministic(Ceed ceed, bool is_deterministic) {
555   ceed->is_deterministic = is_deterministic;
556   return CEED_ERROR_SUCCESS;
557 }
558 
559 /**
560   @brief Set a backend function
561 
562   This function is used for a backend to set the function associated with the Ceed objects.
563     For example, CeedSetBackendFunction(ceed, "Ceed", ceed, "VectorCreate", BackendVectorCreate) sets the backend implementation of 'CeedVectorCreate'
564 and CeedSetBackendFunction(ceed, "Basis", basis, "Apply", BackendBasisApply) sets the backend implementation of 'CeedBasisApply'. Note, the prefix
565 'Ceed' is not required for the object type ("Basis" vs "CeedBasis").
566 
567   @param[in]  ceed      Ceed context for error handling
568   @param[in]  type      Type of Ceed object to set function for
569   @param[out] object    Ceed object to set function for
570   @param[in]  func_name Name of function to set
571   @param[in]  f         Function to set
572 
573   @return An error code: 0 - success, otherwise - failure
574 
575   @ref Backend
576 **/
577 int CeedSetBackendFunction(Ceed ceed, const char *type, void *object, const char *func_name, int (*f)()) {
578   char lookup_name[CEED_MAX_RESOURCE_LEN + 1] = "";
579 
580   // Build lookup name
581   if (strcmp(type, "Ceed")) strncat(lookup_name, "Ceed", CEED_MAX_RESOURCE_LEN);
582   strncat(lookup_name, type, CEED_MAX_RESOURCE_LEN);
583   strncat(lookup_name, func_name, CEED_MAX_RESOURCE_LEN);
584 
585   // Find and use offset
586   for (CeedInt i = 0; ceed->f_offsets[i].func_name; i++) {
587     if (!strcmp(ceed->f_offsets[i].func_name, lookup_name)) {
588       size_t offset          = ceed->f_offsets[i].offset;
589       int (**fpointer)(void) = (int (**)(void))((char *)object + offset);  // *NOPAD*
590       *fpointer              = f;
591       return CEED_ERROR_SUCCESS;
592     }
593   }
594 
595   // LCOV_EXCL_START
596   return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Requested function '%s' was not found for CEED object '%s'", func_name, type);
597   // LCOV_EXCL_STOP
598 }
599 
600 /**
601   @brief Retrieve backend data for a Ceed context
602 
603   @param[in]  ceed Ceed context to retrieve data of
604   @param[out] data Address to save data to
605 
606   @return An error code: 0 - success, otherwise - failure
607 
608   @ref Backend
609 **/
610 int CeedGetData(Ceed ceed, void *data) {
611   *(void **)data = ceed->data;
612   return CEED_ERROR_SUCCESS;
613 }
614 
615 /**
616   @brief Set backend data for a Ceed context
617 
618   @param[in,out] ceed Ceed context to set data of
619   @param[in]     data Address of data to set
620 
621   @return An error code: 0 - success, otherwise - failure
622 
623   @ref Backend
624 **/
625 int CeedSetData(Ceed ceed, void *data) {
626   ceed->data = data;
627   return CEED_ERROR_SUCCESS;
628 }
629 
630 /**
631   @brief Increment the reference counter for a Ceed context
632 
633   @param[in,out] ceed Ceed context to increment the reference counter
634 
635   @return An error code: 0 - success, otherwise - failure
636 
637   @ref Backend
638 **/
639 int CeedReference(Ceed ceed) {
640   ceed->ref_count++;
641   return CEED_ERROR_SUCCESS;
642 }
643 
644 /// @}
645 
646 /// ----------------------------------------------------------------------------
647 /// Ceed Public API
648 /// ----------------------------------------------------------------------------
649 /// @addtogroup CeedUser
650 /// @{
651 
652 /**
653   @brief Get the list of available resource names for Ceed contexts
654            Note: The caller is responsible for `free()`ing the resources and priorities arrays, but should not `free()` the contents of the resources
655 array.
656 
657   @param[out] n          Number of available resources
658   @param[out] resources  List of available resource names
659   @param[out] priorities Resource name prioritization values, lower is better
660 
661   @return An error code: 0 - success, otherwise - failure
662 
663   @ref User
664 **/
665 // LCOV_EXCL_START
666 int CeedRegistryGetList(size_t *n, char ***const resources, CeedInt **priorities) {
667   *n         = 0;
668   *resources = malloc(num_backends * sizeof(**resources));
669   if (!resources) return CeedError(NULL, CEED_ERROR_MAJOR, "malloc() failure");
670   if (priorities) {
671     *priorities = malloc(num_backends * sizeof(**priorities));
672     if (!priorities) return CeedError(NULL, CEED_ERROR_MAJOR, "malloc() failure");
673   }
674   for (size_t i = 0; i < num_backends; i++) {
675     // Only report compiled backends
676     if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) {
677       *resources[i] = backends[i].prefix;
678       if (priorities) *priorities[i] = backends[i].priority;
679       *n += 1;
680     }
681   }
682   if (*n == 0) {
683     // LCOV_EXCL_START
684     return CeedError(NULL, CEED_ERROR_MAJOR, "No backends installed");
685     // LCOV_EXCL_STOP
686   }
687   *resources = realloc(*resources, *n * sizeof(**resources));
688   if (!resources) return CeedError(NULL, CEED_ERROR_MAJOR, "realloc() failure");
689   if (priorities) {
690     *priorities = realloc(*priorities, *n * sizeof(**priorities));
691     if (!priorities) return CeedError(NULL, CEED_ERROR_MAJOR, "realloc() failure");
692   }
693   return CEED_ERROR_SUCCESS;
694 }
695 // LCOV_EXCL_STOP
696 
697 /**
698   @brief Initialize a \ref Ceed context to use the specified resource.
699            Note: Prefixing the resource with "help:" (e.g. "help:/cpu/self") will result in CeedInt printing the current libCEED version number and a
700 list of current available backend resources to stderr.
701 
702   @param[in]  resource Resource to use, e.g., "/cpu/self"
703   @param[out] ceed     The library context
704   @sa CeedRegister() CeedDestroy()
705 
706   @return An error code: 0 - success, otherwise - failure
707 
708   @ref User
709 **/
710 int CeedInit(const char *resource, Ceed *ceed) {
711   size_t match_len = 0, match_index = UINT_MAX, match_priority = CEED_MAX_BACKEND_PRIORITY, priority;
712 
713   // Find matching backend
714   if (!resource) {
715     // LCOV_EXCL_START
716     return CeedError(NULL, CEED_ERROR_MAJOR, "No resource provided");
717     // LCOV_EXCL_STOP
718   }
719   CeedCall(CeedRegisterAll());
720 
721   // Check for help request
722   const char *help_prefix = "help";
723   size_t      match_help  = 0;
724   while (match_help < 4 && resource[match_help] == help_prefix[match_help]) match_help++;
725   if (match_help == 4) {
726     fprintf(stderr, "libCEED version: %d.%d%d%s\n", CEED_VERSION_MAJOR, CEED_VERSION_MINOR, CEED_VERSION_PATCH,
727             CEED_VERSION_RELEASE ? "" : "+development");
728     fprintf(stderr, "Available backend resources:\n");
729     for (size_t i = 0; i < num_backends; i++) {
730       // Only report compiled backends
731       if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) fprintf(stderr, "  %s\n", backends[i].prefix);
732     }
733     fflush(stderr);
734     match_help = 5;  // Delineating character expected
735   } else {
736     match_help = 0;
737   }
738 
739   // Find best match, computed as number of matching characters from requested resource stem
740   size_t stem_length = 0;
741   while (resource[stem_length + match_help] && resource[stem_length + match_help] != ':') stem_length++;
742   for (size_t i = 0; i < num_backends; i++) {
743     size_t      n      = 0;
744     const char *prefix = backends[i].prefix;
745     while (prefix[n] && prefix[n] == resource[n + match_help]) n++;
746     priority = backends[i].priority;
747     if (n > match_len || (n == match_len && match_priority > priority)) {
748       match_len      = n;
749       match_priority = priority;
750       match_index    = i;
751     }
752   }
753   // Using Levenshtein distance to find closest match
754   if (match_len <= 1 || match_len != stem_length) {
755     // LCOV_EXCL_START
756     size_t lev_dis   = UINT_MAX;
757     size_t lev_index = UINT_MAX, lev_priority = CEED_MAX_BACKEND_PRIORITY;
758     for (size_t i = 0; i < num_backends; i++) {
759       const char *prefix        = backends[i].prefix;
760       size_t      prefix_length = strlen(backends[i].prefix);
761       size_t      min_len       = (prefix_length < stem_length) ? prefix_length : stem_length;
762       size_t      column[min_len + 1];
763       for (size_t j = 0; j <= min_len; j++) column[j] = j;
764       for (size_t j = 1; j <= min_len; j++) {
765         column[0] = j;
766         for (size_t k = 1, last_diag = j - 1; k <= min_len; k++) {
767           size_t old_diag = column[k];
768           size_t min_1    = (column[k] < column[k - 1]) ? column[k] + 1 : column[k - 1] + 1;
769           size_t min_2    = last_diag + (resource[k - 1] == prefix[j - 1] ? 0 : 1);
770           column[k]       = (min_1 < min_2) ? min_1 : min_2;
771           last_diag       = old_diag;
772         }
773       }
774       size_t n = column[min_len];
775       priority = backends[i].priority;
776       if (n < lev_dis || (n == lev_dis && lev_priority > priority)) {
777         lev_dis      = n;
778         lev_priority = priority;
779         lev_index    = i;
780       }
781     }
782     const char *prefix_lev = backends[lev_index].prefix;
783     size_t      lev_length = 0;
784     while (prefix_lev[lev_length] && prefix_lev[lev_length] != '\0') lev_length++;
785     size_t m = (lev_length < stem_length) ? lev_length : stem_length;
786     if (lev_dis + 1 >= m) {
787       return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s", resource);
788     } else {
789       return CeedError(NULL, CEED_ERROR_MAJOR,
790                        "No suitable backend: %s\n"
791                        "Closest match: %s",
792                        resource, backends[lev_index].prefix);
793     }
794     // LCOV_EXCL_STOP
795   }
796 
797   // Setup Ceed
798   CeedCall(CeedCalloc(1, ceed));
799   CeedCall(CeedCalloc(1, &(*ceed)->jit_source_roots));
800   const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
801   if (!ceed_error_handler) ceed_error_handler = "abort";
802   if (!strcmp(ceed_error_handler, "exit")) (*ceed)->Error = CeedErrorExit;
803   else if (!strcmp(ceed_error_handler, "store")) (*ceed)->Error = CeedErrorStore;
804   else (*ceed)->Error = CeedErrorAbort;
805   memcpy((*ceed)->err_msg, "No error message stored", 24);
806   (*ceed)->ref_count = 1;
807   (*ceed)->data      = NULL;
808 
809   // Set lookup table
810   FOffset f_offsets[] = {
811       CEED_FTABLE_ENTRY(Ceed, Error),
812       CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
813       CEED_FTABLE_ENTRY(Ceed, Destroy),
814       CEED_FTABLE_ENTRY(Ceed, VectorCreate),
815       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
816       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateOriented),
817       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
818       CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
819       CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
820       CEED_FTABLE_ENTRY(Ceed, BasisCreateHdiv),
821       CEED_FTABLE_ENTRY(Ceed, TensorContractCreate),
822       CEED_FTABLE_ENTRY(Ceed, QFunctionCreate),
823       CEED_FTABLE_ENTRY(Ceed, QFunctionContextCreate),
824       CEED_FTABLE_ENTRY(Ceed, OperatorCreate),
825       CEED_FTABLE_ENTRY(Ceed, CompositeOperatorCreate),
826       CEED_FTABLE_ENTRY(CeedVector, HasValidArray),
827       CEED_FTABLE_ENTRY(CeedVector, HasBorrowedArrayOfType),
828       CEED_FTABLE_ENTRY(CeedVector, SetArray),
829       CEED_FTABLE_ENTRY(CeedVector, TakeArray),
830       CEED_FTABLE_ENTRY(CeedVector, SetValue),
831       CEED_FTABLE_ENTRY(CeedVector, SyncArray),
832       CEED_FTABLE_ENTRY(CeedVector, GetArray),
833       CEED_FTABLE_ENTRY(CeedVector, GetArrayRead),
834       CEED_FTABLE_ENTRY(CeedVector, GetArrayWrite),
835       CEED_FTABLE_ENTRY(CeedVector, RestoreArray),
836       CEED_FTABLE_ENTRY(CeedVector, RestoreArrayRead),
837       CEED_FTABLE_ENTRY(CeedVector, Norm),
838       CEED_FTABLE_ENTRY(CeedVector, Scale),
839       CEED_FTABLE_ENTRY(CeedVector, AXPY),
840       CEED_FTABLE_ENTRY(CeedVector, PointwiseMult),
841       CEED_FTABLE_ENTRY(CeedVector, Reciprocal),
842       CEED_FTABLE_ENTRY(CeedVector, Destroy),
843       CEED_FTABLE_ENTRY(CeedElemRestriction, Apply),
844       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyBlock),
845       CEED_FTABLE_ENTRY(CeedElemRestriction, GetOffsets),
846       CEED_FTABLE_ENTRY(CeedElemRestriction, Destroy),
847       CEED_FTABLE_ENTRY(CeedBasis, Apply),
848       CEED_FTABLE_ENTRY(CeedBasis, Destroy),
849       CEED_FTABLE_ENTRY(CeedTensorContract, Apply),
850       CEED_FTABLE_ENTRY(CeedTensorContract, Destroy),
851       CEED_FTABLE_ENTRY(CeedQFunction, Apply),
852       CEED_FTABLE_ENTRY(CeedQFunction, SetCUDAUserFunction),
853       CEED_FTABLE_ENTRY(CeedQFunction, SetHIPUserFunction),
854       CEED_FTABLE_ENTRY(CeedQFunction, Destroy),
855       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasValidData),
856       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasBorrowedDataOfType),
857       CEED_FTABLE_ENTRY(CeedQFunctionContext, SetData),
858       CEED_FTABLE_ENTRY(CeedQFunctionContext, TakeData),
859       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetData),
860       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetDataRead),
861       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreData),
862       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreDataRead),
863       CEED_FTABLE_ENTRY(CeedQFunctionContext, DataDestroy),
864       CEED_FTABLE_ENTRY(CeedQFunctionContext, Destroy),
865       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunction),
866       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunctionUpdate),
867       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleDiagonal),
868       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddDiagonal),
869       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemblePointBlockDiagonal),
870       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddPointBlockDiagonal),
871       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSymbolic),
872       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemble),
873       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSingle),
874       CEED_FTABLE_ENTRY(CeedOperator, CreateFDMElementInverse),
875       CEED_FTABLE_ENTRY(CeedOperator, Apply),
876       CEED_FTABLE_ENTRY(CeedOperator, ApplyComposite),
877       CEED_FTABLE_ENTRY(CeedOperator, ApplyAdd),
878       CEED_FTABLE_ENTRY(CeedOperator, ApplyAddComposite),
879       CEED_FTABLE_ENTRY(CeedOperator, ApplyJacobian),
880       CEED_FTABLE_ENTRY(CeedOperator, Destroy),
881       {NULL, 0}  // End of lookup table - used in SetBackendFunction loop
882   };
883 
884   CeedCall(CeedCalloc(sizeof(f_offsets), &(*ceed)->f_offsets));
885   memcpy((*ceed)->f_offsets, f_offsets, sizeof(f_offsets));
886 
887   // Set fallback for advanced CeedOperator functions
888   const char fallbackresource[] = "";
889   CeedCall(CeedSetOperatorFallbackResource(*ceed, fallbackresource));
890 
891   // Record env variables CEED_DEBUG or DBG
892   (*ceed)->is_debug = !!getenv("CEED_DEBUG") || !!getenv("DEBUG") || !!getenv("DBG");
893 
894   // Copy resource prefix, if backend setup successful
895   CeedCall(CeedStringAllocCopy(backends[match_index].prefix, (char **)&(*ceed)->resource));
896 
897   // Set default JiT source root
898   // Note: there will always be the default root for every Ceed but all additional paths are added to the top-most parent
899   CeedCall(CeedAddJitSourceRoot(*ceed, (char *)CeedJitSourceRootDefault));
900 
901   // Backend specific setup
902   CeedCall(backends[match_index].init(&resource[match_help], *ceed));
903 
904   return CEED_ERROR_SUCCESS;
905 }
906 
907 /**
908   @brief Copy the pointer to a Ceed context.
909            Both pointers should be destroyed with `CeedDestroy()`;
910            Note: If `*ceed_copy` is non-NULL, then it is assumed that `*ceed_copy` is a pointer to a Ceed context.
911              This Ceed context will be destroyed if `*ceed_copy` is the only reference to this Ceed context.
912 
913   @param[in]     ceed      Ceed context to copy reference to
914   @param[in,out] ceed_copy Variable to store copied reference
915 
916   @return An error code: 0 - success, otherwise - failure
917 
918   @ref User
919 **/
920 int CeedReferenceCopy(Ceed ceed, Ceed *ceed_copy) {
921   CeedCall(CeedReference(ceed));
922   CeedCall(CeedDestroy(ceed_copy));
923   *ceed_copy = ceed;
924   return CEED_ERROR_SUCCESS;
925 }
926 
927 /**
928   @brief Get the full resource name for a Ceed context
929 
930   @param[in]  ceed     Ceed context to get resource name of
931   @param[out] resource Variable to store resource name
932 
933   @return An error code: 0 - success, otherwise - failure
934 
935   @ref User
936 **/
937 int CeedGetResource(Ceed ceed, const char **resource) {
938   *resource = (const char *)ceed->resource;
939   return CEED_ERROR_SUCCESS;
940 }
941 
942 /**
943   @brief Return Ceed context preferred memory type
944 
945   @param[in]  ceed     Ceed context to get preferred memory type of
946   @param[out] mem_type Address to save preferred memory type to
947 
948   @return An error code: 0 - success, otherwise - failure
949 
950   @ref User
951 **/
952 int CeedGetPreferredMemType(Ceed ceed, CeedMemType *mem_type) {
953   if (ceed->GetPreferredMemType) {
954     CeedCall(ceed->GetPreferredMemType(mem_type));
955   } else {
956     Ceed delegate;
957     CeedCall(CeedGetDelegate(ceed, &delegate));
958 
959     if (delegate) {
960       CeedCall(CeedGetPreferredMemType(delegate, mem_type));
961     } else {
962       *mem_type = CEED_MEM_HOST;
963     }
964   }
965   return CEED_ERROR_SUCCESS;
966 }
967 
968 /**
969   @brief Get deterministic status of Ceed
970 
971   @param[in]  ceed             Ceed
972   @param[out] is_deterministic Variable to store deterministic status
973 
974   @return An error code: 0 - success, otherwise - failure
975 
976   @ref User
977 **/
978 int CeedIsDeterministic(Ceed ceed, bool *is_deterministic) {
979   *is_deterministic = ceed->is_deterministic;
980   return CEED_ERROR_SUCCESS;
981 }
982 
983 /**
984   @brief Set additional JiT source root for Ceed
985 
986   @param[in,out] ceed            Ceed
987   @param[in]     jit_source_root Absolute path to additional JiT source directory
988 
989   @return An error code: 0 - success, otherwise - failure
990 
991   @ref User
992 **/
993 int CeedAddJitSourceRoot(Ceed ceed, const char *jit_source_root) {
994   Ceed ceed_parent;
995 
996   CeedCall(CeedGetParent(ceed, &ceed_parent));
997 
998   CeedInt index       = ceed_parent->num_jit_source_roots;
999   size_t  path_length = strlen(jit_source_root);
1000   CeedCall(CeedRealloc(index + 1, &ceed_parent->jit_source_roots));
1001   CeedCall(CeedCalloc(path_length + 1, &ceed_parent->jit_source_roots[index]));
1002   memcpy(ceed_parent->jit_source_roots[index], jit_source_root, path_length);
1003   ceed_parent->num_jit_source_roots++;
1004 
1005   return CEED_ERROR_SUCCESS;
1006 }
1007 
1008 /**
1009   @brief View a Ceed
1010 
1011   @param[in] ceed   Ceed to view
1012   @param[in] stream Filestream to write to
1013 
1014   @return An error code: 0 - success, otherwise - failure
1015 
1016   @ref User
1017 **/
1018 int CeedView(Ceed ceed, FILE *stream) {
1019   CeedMemType mem_type;
1020 
1021   CeedCall(CeedGetPreferredMemType(ceed, &mem_type));
1022 
1023   fprintf(stream,
1024           "Ceed\n"
1025           "  Ceed Resource: %s\n"
1026           "  Preferred MemType: %s\n",
1027           ceed->resource, CeedMemTypes[mem_type]);
1028   return CEED_ERROR_SUCCESS;
1029 }
1030 
1031 /**
1032   @brief Destroy a Ceed context
1033 
1034   @param[in,out] ceed Address of Ceed context to destroy
1035 
1036   @return An error code: 0 - success, otherwise - failure
1037 
1038   @ref User
1039 **/
1040 int CeedDestroy(Ceed *ceed) {
1041   if (!*ceed || --(*ceed)->ref_count > 0) return CEED_ERROR_SUCCESS;
1042   if ((*ceed)->delegate) CeedCall(CeedDestroy(&(*ceed)->delegate));
1043 
1044   if ((*ceed)->obj_delegate_count > 0) {
1045     for (CeedInt i = 0; i < (*ceed)->obj_delegate_count; i++) {
1046       CeedCall(CeedDestroy(&((*ceed)->obj_delegates[i].delegate)));
1047       CeedCall(CeedFree(&(*ceed)->obj_delegates[i].obj_name));
1048     }
1049     CeedCall(CeedFree(&(*ceed)->obj_delegates));
1050   }
1051 
1052   if ((*ceed)->Destroy) CeedCall((*ceed)->Destroy(*ceed));
1053 
1054   for (CeedInt i = 0; i < (*ceed)->num_jit_source_roots; i++) {
1055     CeedCall(CeedFree(&(*ceed)->jit_source_roots[i]));
1056   }
1057   CeedCall(CeedFree(&(*ceed)->jit_source_roots));
1058 
1059   CeedCall(CeedFree(&(*ceed)->f_offsets));
1060   CeedCall(CeedFree(&(*ceed)->resource));
1061   CeedCall(CeedDestroy(&(*ceed)->op_fallback_ceed));
1062   CeedCall(CeedFree(&(*ceed)->op_fallback_resource));
1063   CeedCall(CeedFree(ceed));
1064   return CEED_ERROR_SUCCESS;
1065 }
1066 
1067 // LCOV_EXCL_START
1068 const char *CeedErrorFormat(Ceed ceed, const char *format, va_list *args) {
1069   if (ceed->parent) return CeedErrorFormat(ceed->parent, format, args);
1070   if (ceed->op_fallback_parent) return CeedErrorFormat(ceed->op_fallback_parent, format, args);
1071   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1072   vsnprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, format, *args);  // NOLINT
1073   return ceed->err_msg;
1074 }
1075 // LCOV_EXCL_STOP
1076 
1077 /**
1078   @brief Error handling implementation; use \ref CeedError instead.
1079 
1080   @ref Developer
1081 **/
1082 int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func, int ecode, const char *format, ...) {
1083   va_list args;
1084   int     ret_val;
1085   va_start(args, format);
1086   if (ceed) {
1087     ret_val = ceed->Error(ceed, filename, lineno, func, ecode, format, &args);
1088   } else {
1089     // LCOV_EXCL_START
1090     const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1091     if (!ceed_error_handler) ceed_error_handler = "abort";
1092     if (!strcmp(ceed_error_handler, "return")) ret_val = CeedErrorReturn(ceed, filename, lineno, func, ecode, format, &args);
1093     else
1094       // This function will not return
1095       ret_val = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, &args);
1096   }
1097   va_end(args);
1098   return ret_val;
1099   // LCOV_EXCL_STOP
1100 }
1101 
1102 /**
1103   @brief Error handler that returns without printing anything.
1104 
1105   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1106 
1107   @ref Developer
1108 **/
1109 // LCOV_EXCL_START
1110 int CeedErrorReturn(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1111   return err_code;
1112 }
1113 // LCOV_EXCL_STOP
1114 
1115 /**
1116   @brief Error handler that stores the error message for future use and returns the error.
1117 
1118   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1119 
1120   @ref Developer
1121 **/
1122 // LCOV_EXCL_START
1123 int CeedErrorStore(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1124   if (ceed->parent) return CeedErrorStore(ceed->parent, filename, line_no, func, err_code, format, args);
1125   if (ceed->op_fallback_parent) return CeedErrorStore(ceed->op_fallback_parent, filename, line_no, func, err_code, format, args);
1126 
1127   // Build message
1128   int len;
1129   len = snprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, "%s:%d in %s(): ", filename, line_no, func);
1130   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1131   vsnprintf(ceed->err_msg + len, CEED_MAX_RESOURCE_LEN - len, format, *args);  // NOLINT
1132   return err_code;
1133 }
1134 // LCOV_EXCL_STOP
1135 
1136 /**
1137   @brief Error handler that prints to stderr and aborts
1138 
1139   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1140 
1141   @ref Developer
1142 **/
1143 // LCOV_EXCL_START
1144 int CeedErrorAbort(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1145   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1146   vfprintf(stderr, format, *args);
1147   fprintf(stderr, "\n");
1148   abort();
1149   return err_code;
1150 }
1151 // LCOV_EXCL_STOP
1152 
1153 /**
1154   @brief Error handler that prints to stderr and exits
1155 
1156   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1157 
1158   In contrast to CeedErrorAbort(), this exits without a signal, so atexit() handlers (e.g., as used by gcov) are run.
1159 
1160   @ref Developer
1161 **/
1162 int CeedErrorExit(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1163   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1164   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1165   vfprintf(stderr, format, *args);  // NOLINT
1166   fprintf(stderr, "\n");
1167   exit(err_code);
1168   return err_code;
1169 }
1170 
1171 /**
1172   @brief Set error handler
1173 
1174   A default error handler is set in CeedInit().
1175   Use this function to change the error handler to CeedErrorReturn(), CeedErrorAbort(), or a user-defined error handler.
1176 
1177   @ref Developer
1178 **/
1179 int CeedSetErrorHandler(Ceed ceed, CeedErrorHandler handler) {
1180   ceed->Error = handler;
1181   if (ceed->delegate) CeedSetErrorHandler(ceed->delegate, handler);
1182   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) CeedSetErrorHandler(ceed->obj_delegates[i].delegate, handler);
1183   return CEED_ERROR_SUCCESS;
1184 }
1185 
1186 /**
1187   @brief Get error message
1188 
1189   The error message is only stored when using the error handler CeedErrorStore()
1190 
1191   @param[in]  ceed    Ceed context to retrieve error message
1192   @param[out] err_msg Char pointer to hold error message
1193 
1194   @ref Developer
1195 **/
1196 int CeedGetErrorMessage(Ceed ceed, const char **err_msg) {
1197   if (ceed->parent) return CeedGetErrorMessage(ceed->parent, err_msg);
1198   if (ceed->op_fallback_parent) return CeedGetErrorMessage(ceed->op_fallback_parent, err_msg);
1199   *err_msg = ceed->err_msg;
1200   return CEED_ERROR_SUCCESS;
1201 }
1202 
1203 /**
1204   @brief Restore error message
1205 
1206   The error message is only stored when using the error handler CeedErrorStore()
1207 
1208   @param[in]  ceed    Ceed context to restore error message
1209   @param[out] err_msg Char pointer that holds error message
1210 
1211   @ref Developer
1212 **/
1213 int CeedResetErrorMessage(Ceed ceed, const char **err_msg) {
1214   if (ceed->parent) return CeedResetErrorMessage(ceed->parent, err_msg);
1215   if (ceed->op_fallback_parent) return CeedResetErrorMessage(ceed->op_fallback_parent, err_msg);
1216   *err_msg = NULL;
1217   memcpy(ceed->err_msg, "No error message stored", 24);
1218   return CEED_ERROR_SUCCESS;
1219 }
1220 
1221 /**
1222   @brief Get libCEED library version info
1223 
1224   libCEED version numbers have the form major.minor.patch.
1225   Non-release versions may contain unstable interfaces.
1226 
1227   @param[out] major   Major version of the library
1228   @param[out] minor   Minor version of the library
1229   @param[out] patch   Patch (subminor) version of the library
1230   @param[out] release True for releases; false for development branches.
1231 
1232   The caller may pass NULL for any arguments that are not needed.
1233 
1234   @sa CEED_VERSION_GE()
1235 
1236   @ref Developer
1237 */
1238 int CeedGetVersion(int *major, int *minor, int *patch, bool *release) {
1239   if (major) *major = CEED_VERSION_MAJOR;
1240   if (minor) *minor = CEED_VERSION_MINOR;
1241   if (patch) *patch = CEED_VERSION_PATCH;
1242   if (release) *release = CEED_VERSION_RELEASE;
1243   return 0;
1244 }
1245 
1246 int CeedGetScalarType(CeedScalarType *scalar_type) {
1247   *scalar_type = CEED_SCALAR_TYPE;
1248   return 0;
1249 }
1250 
1251 /// @}
1252