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