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