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