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