xref: /libCEED/interface/ceed.c (revision 73b5a3bf55aeb5ad8a797943274ffe079610ce9e)
1 // Copyright (c) 2017-2025, 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) {#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 immediately.
44   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 order that it is submitted to the device.
66   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 @ref CeedRequest to complete.
86 
87   Calling @ref CeedRequestWait() on a `NULL` request is a no-op.
88 
89   @param[in,out] req Address of @ref 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 
111   Note: Backends should call @ref CeedRegister() instead.
112 
113   @param[in] prefix   Prefix of resources for this backend to respond to.
114                         For example, the reference backend responds to "/cpu/self".
115   @param[in] init     Initialization function called by @ref CeedInit() when the backend is selected to drive the requested resource
116   @param[in] priority Integer priority.
117                         Lower values are preferred in case the resource requested by @ref CeedInit() has non-unique best prefix match.
118 
119   @return An error code: 0 - success, otherwise - failure
120 
121   @ref Developer
122 **/
123 int CeedRegisterImpl(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
124   int ierr = 0;
125 
126   CeedPragmaCritical(CeedRegisterImpl) {
127     if (num_backends < sizeof(backends) / sizeof(backends[0])) {
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     } else {
134       ierr = 1;
135     }
136   }
137   CeedCheck(ierr == 0, NULL, CEED_ERROR_MAJOR, "Too many backends");
138   return CEED_ERROR_SUCCESS;
139 }
140 
141 /**
142   @brief Create a work vector space for a `ceed`
143 
144   @param[in,out] ceed `Ceed` to create work vector space for
145 
146   @return An error code: 0 - success, otherwise - failure
147 
148   @ref Developer
149 **/
150 static int CeedWorkVectorsCreate(Ceed ceed) {
151   CeedCall(CeedCalloc(1, &ceed->work_vectors));
152   return CEED_ERROR_SUCCESS;
153 }
154 
155 /**
156   @brief Destroy a work vector space for a `ceed`
157 
158   @param[in,out] ceed `Ceed` to destroy work vector space for
159 
160   @return An error code: 0 - success, otherwise - failure
161 
162   @ref Developer
163 **/
164 static int CeedWorkVectorsDestroy(Ceed ceed) {
165   if (!ceed->work_vectors) return CEED_ERROR_SUCCESS;
166   for (CeedSize i = 0; i < ceed->work_vectors->num_vecs; i++) {
167     CeedCheck(!ceed->work_vectors->is_in_use[i], ceed, CEED_ERROR_ACCESS, "Work vector %" CeedSize_FMT " checked out but not returned");
168     ceed->ref_count += 2;  // Note: increase ref_count to prevent Ceed destructor from triggering again
169     CeedCall(CeedVectorDestroy(&ceed->work_vectors->vecs[i]));
170     ceed->ref_count -= 1;  // Note: restore ref_count
171   }
172   CeedCall(CeedFree(&ceed->work_vectors->is_in_use));
173   CeedCall(CeedFree(&ceed->work_vectors->vecs));
174   CeedCall(CeedFree(&ceed->work_vectors));
175   return CEED_ERROR_SUCCESS;
176 }
177 
178 /// @}
179 
180 /// ----------------------------------------------------------------------------
181 /// Ceed Backend API
182 /// ----------------------------------------------------------------------------
183 /// @addtogroup CeedBackend
184 /// @{
185 
186 /**
187   @brief Return value of `CEED_DEBUG` environment variable
188 
189   @param[in] ceed `Ceed` context
190 
191   @return Boolean value: true  - debugging mode enabled
192                          false - debugging mode disabled
193 
194   @ref Backend
195 **/
196 // LCOV_EXCL_START
197 bool CeedDebugFlag(const Ceed ceed) { return ceed->is_debug; }
198 // LCOV_EXCL_STOP
199 
200 /**
201   @brief Return value of `CEED_DEBUG` environment variable
202 
203   @return Boolean value: true  - debugging mode enabled
204                          false - debugging mode disabled
205 
206   @ref Backend
207 **/
208 // LCOV_EXCL_START
209 bool CeedDebugFlagEnv(void) { return getenv("CEED_DEBUG") || getenv("DEBUG") || getenv("DBG"); }
210 // LCOV_EXCL_STOP
211 
212 /**
213   @brief Print debugging information in color
214 
215   @param[in] color  Color to print
216   @param[in] format Printing format
217 
218   @ref Backend
219 **/
220 // LCOV_EXCL_START
221 void CeedDebugImpl256(const unsigned char color, const char *format, ...) {
222   va_list args;
223   va_start(args, format);
224   fflush(stdout);
225   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[38;5;%dm", color);
226   vfprintf(stdout, format, args);
227   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[m");
228   fprintf(stdout, "\n");
229   fflush(stdout);
230   va_end(args);
231 }
232 // LCOV_EXCL_STOP
233 
234 /**
235   @brief Allocate an array on the host; use @ref CeedMalloc().
236 
237   Memory usage can be tracked by the library.
238   This ensures sufficient alignment for vectorization and should be used for large allocations.
239 
240   @param[in]  n    Number of units to allocate
241   @param[in]  unit Size of each unit
242   @param[out] p    Address of pointer to hold the result
243 
244   @return An error code: 0 - success, otherwise - failure
245 
246   @ref Backend
247 
248   @sa CeedFree()
249 **/
250 int CeedMallocArray(size_t n, size_t unit, void *p) {
251   int ierr = posix_memalign((void **)p, CEED_ALIGN, n * unit);
252   CeedCheck(ierr == 0, NULL, CEED_ERROR_MAJOR, "posix_memalign failed to allocate %zd members of size %zd\n", n, unit);
253   return CEED_ERROR_SUCCESS;
254 }
255 
256 /**
257   @brief Allocate a cleared (zeroed) array on the host; use @ref CeedCalloc().
258 
259   Memory usage can be tracked by the library.
260 
261   @param[in]  n    Number of units to allocate
262   @param[in]  unit Size of each unit
263   @param[out] p    Address of pointer to hold the result
264 
265   @return An error code: 0 - success, otherwise - failure
266 
267   @ref Backend
268 
269   @sa CeedFree()
270 **/
271 int CeedCallocArray(size_t n, size_t unit, void *p) {
272   *(void **)p = calloc(n, unit);
273   CeedCheck(!n || !unit || *(void **)p, NULL, CEED_ERROR_MAJOR, "calloc failed to allocate %zd members of size %zd\n", n, unit);
274   return CEED_ERROR_SUCCESS;
275 }
276 
277 /**
278   @brief Reallocate an array on the host; use @ref CeedRealloc().
279 
280   Memory usage can be tracked by the library.
281 
282   @param[in]  n    Number of units to allocate
283   @param[in]  unit Size of each unit
284   @param[out] p    Address of pointer to hold the result
285 
286   @return An error code: 0 - success, otherwise - failure
287 
288   @ref Backend
289 
290   @sa CeedFree()
291 **/
292 int CeedReallocArray(size_t n, size_t unit, void *p) {
293   *(void **)p = realloc(*(void **)p, n * unit);
294   CeedCheck(!n || !unit || *(void **)p, NULL, CEED_ERROR_MAJOR, "realloc failed to allocate %zd members of size %zd\n", n, unit);
295   return CEED_ERROR_SUCCESS;
296 }
297 
298 /**
299   @brief Allocate a cleared string buffer on the host.
300 
301   Memory usage can be tracked by the library.
302 
303   @param[in]  source Pointer to string to be copied
304   @param[out] copy   Pointer to variable to hold newly allocated string copy
305 
306   @return An error code: 0 - success, otherwise - failure
307 
308   @ref Backend
309 
310   @sa CeedFree()
311 **/
312 int CeedStringAllocCopy(const char *source, char **copy) {
313   size_t len = strlen(source);
314   CeedCall(CeedCalloc(len + 1, copy));
315   memcpy(*copy, source, len);
316   return CEED_ERROR_SUCCESS;
317 }
318 
319 /** Free memory allocated using @ref CeedMalloc() or @ref CeedCalloc()
320 
321   @param[in,out] p Address of pointer to memory.
322                      This argument is of type `void*` to avoid needing a cast, but is the address of the pointer (which is zeroed) rather than the pointer.
323 
324   @return An error code: 0 - success, otherwise - failure
325 
326   @ref Backend
327 **/
328 int CeedFree(void *p) {
329   free(*(void **)p);
330   *(void **)p = NULL;
331   return CEED_ERROR_SUCCESS;
332 }
333 
334 /** Internal helper to manage handoff of user `source_array` to backend with proper @ref CeedCopyMode behavior.
335 
336   @param[in]     source_array          Source data provided by user
337   @param[in]     copy_mode             Copy mode for the data
338   @param[in]     num_values            Number of values to handle
339   @param[in]     size_unit             Size of array element in bytes
340   @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
341   @param[out]    target_array_borrowed Pointer to location to hold borrowed data
342   @param[out]    target_array          Pointer to location for data
343 
344   @return An error code: 0 - success, otherwise - failure
345 
346   @ref Backend
347 **/
348 static inline int CeedSetHostGenericArray(const void *source_array, CeedCopyMode copy_mode, size_t size_unit, CeedSize num_values,
349                                           void *target_array_owned, void *target_array_borrowed, void *target_array) {
350   switch (copy_mode) {
351     case CEED_COPY_VALUES:
352       if (!*(void **)target_array) {
353         if (*(void **)target_array_borrowed) {
354           *(void **)target_array = *(void **)target_array_borrowed;
355         } else {
356           if (!*(void **)target_array_owned) CeedCall(CeedCallocArray(num_values, size_unit, target_array_owned));
357           *(void **)target_array = *(void **)target_array_owned;
358         }
359       }
360       if (source_array) memcpy(*(void **)target_array, source_array, size_unit * num_values);
361       break;
362     case CEED_OWN_POINTER:
363       CeedCall(CeedFree(target_array_owned));
364       *(void **)target_array_owned    = (void *)source_array;
365       *(void **)target_array_borrowed = NULL;
366       *(void **)target_array          = *(void **)target_array_owned;
367       break;
368     case CEED_USE_POINTER:
369       CeedCall(CeedFree(target_array_owned));
370       *(void **)target_array_owned    = NULL;
371       *(void **)target_array_borrowed = (void *)source_array;
372       *(void **)target_array          = *(void **)target_array_borrowed;
373   }
374   return CEED_ERROR_SUCCESS;
375 }
376 
377 /** Manage handoff of user `bool` `source_array` to backend with proper @ref CeedCopyMode behavior.
378 
379   @param[in]     source_array          Source data provided by user
380   @param[in]     copy_mode             Copy mode for the data
381   @param[in]     num_values            Number of values to handle
382   @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
383   @param[out]    target_array_borrowed Pointer to location to hold borrowed data
384   @param[out]    target_array          Pointer to location for data
385 
386   @return An error code: 0 - success, otherwise - failure
387 
388   @ref Backend
389 **/
390 int CeedSetHostBoolArray(const bool *source_array, CeedCopyMode copy_mode, CeedSize num_values, const bool **target_array_owned,
391                          const bool **target_array_borrowed, const bool **target_array) {
392   CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(bool), num_values, target_array_owned, target_array_borrowed, target_array));
393   return CEED_ERROR_SUCCESS;
394 }
395 
396 /** Manage handoff of user `CeedInt8` `source_array` to backend with proper @ref CeedCopyMode behavior.
397 
398   @param[in]     source_array          Source data provided by user
399   @param[in]     copy_mode             Copy mode for the data
400   @param[in]     num_values            Number of values to handle
401   @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
402   @param[out]    target_array_borrowed Pointer to location to hold borrowed data
403   @param[out]    target_array          Pointer to location for data
404 
405   @return An error code: 0 - success, otherwise - failure
406 
407   @ref Backend
408 **/
409 int CeedSetHostCeedInt8Array(const CeedInt8 *source_array, CeedCopyMode copy_mode, CeedSize num_values, const CeedInt8 **target_array_owned,
410                              const CeedInt8 **target_array_borrowed, const CeedInt8 **target_array) {
411   CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(CeedInt8), num_values, target_array_owned, target_array_borrowed, target_array));
412   return CEED_ERROR_SUCCESS;
413 }
414 
415 /** Manage handoff of user `CeedInt` `source_array` to backend with proper @ref CeedCopyMode behavior.
416 
417   @param[in]     source_array          Source data provided by user
418   @param[in]     copy_mode             Copy mode for the data
419   @param[in]     num_values            Number of values to handle
420   @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
421   @param[out]    target_array_borrowed Pointer to location to hold borrowed data
422   @param[out]    target_array          Pointer to location for data
423 
424   @return An error code: 0 - success, otherwise - failure
425 
426   @ref Backend
427 **/
428 int CeedSetHostCeedIntArray(const CeedInt *source_array, CeedCopyMode copy_mode, CeedSize num_values, const CeedInt **target_array_owned,
429                             const CeedInt **target_array_borrowed, const CeedInt **target_array) {
430   CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(CeedInt), num_values, target_array_owned, target_array_borrowed, target_array));
431   return CEED_ERROR_SUCCESS;
432 }
433 
434 /** Manage handoff of user `CeedScalar` `source_array` to backend with proper @ref CeedCopyMode behavior.
435 
436   @param[in]     source_array          Source data provided by user
437   @param[in]     copy_mode             Copy mode for the data
438   @param[in]     num_values            Number of values to handle
439   @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
440   @param[out]    target_array_borrowed Pointer to location to hold borrowed data
441   @param[out]    target_array          Pointer to location for data
442 
443   @return An error code: 0 - success, otherwise - failure
444 
445   @ref Backend
446 **/
447 int CeedSetHostCeedScalarArray(const CeedScalar *source_array, CeedCopyMode copy_mode, CeedSize num_values, const CeedScalar **target_array_owned,
448                                const CeedScalar **target_array_borrowed, const CeedScalar **target_array) {
449   CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(CeedScalar), num_values, target_array_owned, target_array_borrowed, target_array));
450   return CEED_ERROR_SUCCESS;
451 }
452 
453 /**
454   @brief Register a `Ceed` backend
455 
456   @param[in] prefix   Prefix of resources for this backend to respond to.
457                         For example, the reference backend responds to "/cpu/self".
458   @param[in] init     Initialization function called by @ref CeedInit() when the backend is selected to drive the requested resource
459   @param[in] priority Integer priority.
460                         Lower values are preferred in case the resource requested by @ref CeedInit() has non-unique best prefix match.
461 
462   @return An error code: 0 - success, otherwise - failure
463 
464   @ref Backend
465 **/
466 int CeedRegister(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
467   CeedDebugEnv("Backend Register: %s", prefix);
468   CeedRegisterImpl(prefix, init, priority);
469   return CEED_ERROR_SUCCESS;
470 }
471 
472 /**
473   @brief Return debugging status flag
474 
475   @param[in]  ceed     `Ceed` context to get debugging flag
476   @param[out] is_debug Variable to store debugging flag
477 
478   @return An error code: 0 - success, otherwise - failure
479 
480   @ref Backend
481 **/
482 int CeedIsDebug(Ceed ceed, bool *is_debug) {
483   *is_debug = ceed->is_debug;
484   return CEED_ERROR_SUCCESS;
485 }
486 
487 /**
488   @brief Get the root of the requested resource.
489 
490   Note: Caller is responsible for calling @ref CeedFree() on the `resource_root`.
491 
492   @param[in]  ceed          `Ceed` context to get resource name of
493   @param[in]  resource      Full user specified resource
494   @param[in]  delineator    Delineator to break `resource_root` and `resource_spec`
495   @param[out] resource_root Variable to store resource root
496 
497   @return An error code: 0 - success, otherwise - failure
498 
499   @ref Backend
500 **/
501 int CeedGetResourceRoot(Ceed ceed, const char *resource, const char *delineator, char **resource_root) {
502   char  *device_spec       = strstr(resource, delineator);
503   size_t resource_root_len = device_spec ? (size_t)(device_spec - resource) + 1 : strlen(resource) + 1;
504 
505   CeedCall(CeedCalloc(resource_root_len, resource_root));
506   memcpy(*resource_root, resource, resource_root_len - 1);
507   return CEED_ERROR_SUCCESS;
508 }
509 
510 /**
511   @brief Retrieve a parent `Ceed` context
512 
513   @param[in]  ceed   `Ceed` context to retrieve parent of
514   @param[out] parent Address to save the parent to
515 
516   @return An error code: 0 - success, otherwise - failure
517 
518   @ref Backend
519 **/
520 int CeedGetParent(Ceed ceed, Ceed *parent) {
521   if (ceed->parent) {
522     CeedCall(CeedGetParent(ceed->parent, parent));
523     return CEED_ERROR_SUCCESS;
524   }
525   *parent = NULL;
526   CeedCall(CeedReferenceCopy(ceed, parent));
527   return CEED_ERROR_SUCCESS;
528 }
529 
530 /**
531   @brief Retrieve a delegate `Ceed` context
532 
533   @param[in]  ceed     `Ceed` context to retrieve delegate of
534   @param[out] delegate Address to save the delegate to
535 
536   @return An error code: 0 - success, otherwise - failure
537 
538   @ref Backend
539 **/
540 int CeedGetDelegate(Ceed ceed, Ceed *delegate) {
541   *delegate = NULL;
542   if (ceed->delegate) CeedCall(CeedReferenceCopy(ceed->delegate, delegate));
543   return CEED_ERROR_SUCCESS;
544 }
545 
546 /**
547   @brief Set a delegate `Ceed` context
548 
549   This function allows a `Ceed` context to set a delegate `Ceed` context.
550   All backend implementations default to the delegate `Ceed` context, unless overridden.
551 
552   @param[in]  ceed     `Ceed` context to set delegate of
553   @param[out] delegate Address to set the delegate to
554 
555   @return An error code: 0 - success, otherwise - failure
556 
557   @ref Backend
558 **/
559 int CeedSetDelegate(Ceed ceed, Ceed delegate) {
560   CeedCall(CeedReferenceCopy(delegate, &ceed->delegate));
561   delegate->parent = ceed;
562   return CEED_ERROR_SUCCESS;
563 }
564 
565 /**
566   @brief Retrieve a delegate `Ceed` context for a specific object type
567 
568   @param[in]  ceed     `Ceed` context to retrieve delegate of
569   @param[out] delegate Address to save the delegate to
570   @param[in]  obj_name Name of the object type to retrieve delegate for
571 
572   @return An error code: 0 - success, otherwise - failure
573 
574   @ref Backend
575 **/
576 int CeedGetObjectDelegate(Ceed ceed, Ceed *delegate, const char *obj_name) {
577   // Check for object delegate
578   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) {
579     if (!strcmp(obj_name, ceed->obj_delegates->obj_name)) {
580       *delegate = NULL;
581       CeedCall(CeedReferenceCopy(ceed->obj_delegates->delegate, delegate));
582       return CEED_ERROR_SUCCESS;
583     }
584   }
585 
586   // Use default delegate if no object delegate
587   CeedCall(CeedGetDelegate(ceed, delegate));
588   return CEED_ERROR_SUCCESS;
589 }
590 
591 /**
592   @brief Set a delegate `Ceed` context for a specific object type
593 
594   This function allows a `Ceed` context to set a delegate `Ceed` context for a given type of `Ceed` object.
595   All backend implementations default to the delegate `Ceed` context for this object.
596   For example, `CeedSetObjectDelegate(ceed, delegate, "Basis")` uses delegate implementations for all `CeedBasis` backend functions.
597 
598   @param[in,out] ceed     `Ceed` context to set delegate of
599   @param[in]     delegate `Ceed` context to use for delegation
600   @param[in]     obj_name Name of the object type to set delegate for
601 
602   @return An error code: 0 - success, otherwise - failure
603 
604   @ref Backend
605 **/
606 int CeedSetObjectDelegate(Ceed ceed, Ceed delegate, const char *obj_name) {
607   CeedInt count = ceed->obj_delegate_count;
608 
609   // Malloc or Realloc
610   if (count) {
611     CeedCall(CeedRealloc(count + 1, &ceed->obj_delegates));
612   } else {
613     CeedCall(CeedCalloc(1, &ceed->obj_delegates));
614   }
615   ceed->obj_delegate_count++;
616 
617   // Set object delegate
618   CeedCall(CeedReferenceCopy(delegate, &ceed->obj_delegates[count].delegate));
619   CeedCall(CeedStringAllocCopy(obj_name, &ceed->obj_delegates[count].obj_name));
620 
621   // Set delegate parent
622   delegate->parent = ceed;
623   return CEED_ERROR_SUCCESS;
624 }
625 
626 /**
627   @brief Get the fallback resource for `CeedOperator`
628 
629   @param[in]  ceed     `Ceed` context
630   @param[out] resource Variable to store fallback resource
631 
632   @return An error code: 0 - success, otherwise - failure
633 
634   @ref Backend
635 **/
636 int CeedGetOperatorFallbackResource(Ceed ceed, const char **resource) {
637   *resource = (const char *)ceed->op_fallback_resource;
638   return CEED_ERROR_SUCCESS;
639 }
640 
641 /**
642   @brief Get the fallback `Ceed` for `CeedOperator`
643 
644   @param[in]  ceed          `Ceed` context
645   @param[out] fallback_ceed Variable to store fallback `Ceed`
646 
647   @return An error code: 0 - success, otherwise - failure
648 
649   @ref Backend
650 **/
651 int CeedGetOperatorFallbackCeed(Ceed ceed, Ceed *fallback_ceed) {
652   if (ceed->has_valid_op_fallback_resource) {
653     CeedDebug256(ceed, CEED_DEBUG_COLOR_SUCCESS, "---------- Ceed Fallback ----------\n");
654     CeedDebug(ceed, "Falling back from Ceed with backend %s at address %p to Ceed with backend %s", ceed->resource, ceed, ceed->op_fallback_resource);
655   }
656 
657   // Create fallback Ceed if uninitialized
658   if (!ceed->op_fallback_ceed && ceed->has_valid_op_fallback_resource) {
659     CeedDebug(ceed, "Creating fallback Ceed");
660 
661     Ceed        fallback_ceed;
662     const char *fallback_resource;
663 
664     CeedCall(CeedGetOperatorFallbackResource(ceed, &fallback_resource));
665     CeedCall(CeedInit(fallback_resource, &fallback_ceed));
666     fallback_ceed->parent  = ceed;
667     fallback_ceed->Error   = ceed->Error;
668     ceed->op_fallback_ceed = fallback_ceed;
669   }
670   *fallback_ceed = NULL;
671   CeedDebug(ceed, "Fallback Ceed with backend %s at address %p\n", ceed->op_fallback_resource, ceed->op_fallback_ceed);
672   if (ceed->op_fallback_ceed) CeedCall(CeedReferenceCopy(ceed->op_fallback_ceed, fallback_ceed));
673   return CEED_ERROR_SUCCESS;
674 }
675 
676 /**
677   @brief Set the fallback resource for `CeedOperator`.
678 
679   The current resource, if any, is freed by calling this function.
680   This string is freed upon the destruction of the `Ceed` context.
681 
682   @param[in,out] ceed     `Ceed` context
683   @param[in]     resource Fallback resource to set
684 
685   @return An error code: 0 - success, otherwise - failure
686 
687   @ref Backend
688 **/
689 int CeedSetOperatorFallbackResource(Ceed ceed, const char *resource) {
690   // Free old
691   CeedCall(CeedFree(&ceed->op_fallback_resource));
692 
693   // Set new
694   CeedCall(CeedStringAllocCopy(resource, (char **)&ceed->op_fallback_resource));
695 
696   // Check validity
697   ceed->has_valid_op_fallback_resource = ceed->op_fallback_resource && ceed->resource && strcmp(ceed->op_fallback_resource, ceed->resource);
698   return CEED_ERROR_SUCCESS;
699 }
700 
701 /**
702   @brief Flag `Ceed` context as deterministic
703 
704   @param[in]  ceed             `Ceed` to flag as deterministic
705   @param[out] is_deterministic Deterministic status to set
706 
707   @return An error code: 0 - success, otherwise - failure
708 
709   @ref Backend
710 **/
711 int CeedSetDeterministic(Ceed ceed, bool is_deterministic) {
712   ceed->is_deterministic = is_deterministic;
713   return CEED_ERROR_SUCCESS;
714 }
715 
716 /**
717   @brief Set a backend function.
718 
719   This function is used for a backend to set the function associated with the Ceed objects.
720   For example, `CeedSetBackendFunction(ceed, "Ceed", ceed, "VectorCreate", BackendVectorCreate)` sets the backend implementation of @ref CeedVectorCreate() and `CeedSetBackendFunction(ceed, "Basis", basis, "Apply", BackendBasisApply)` sets the backend implementation of @ref CeedBasisApply().
721   Note, the prefix 'Ceed' is not required for the object type ("Basis" vs "CeedBasis").
722 
723   @param[in]  ceed      `Ceed` context for error handling
724   @param[in]  type      Type of Ceed object to set function for
725   @param[out] object    Ceed object to set function for
726   @param[in]  func_name Name of function to set
727   @param[in]  f         Function to set
728 
729   @return An error code: 0 - success, otherwise - failure
730 
731   @ref Backend
732 **/
733 int CeedSetBackendFunctionImpl(Ceed ceed, const char *type, void *object, const char *func_name, void (*f)(void)) {
734   char lookup_name[CEED_MAX_RESOURCE_LEN + 1] = "";
735 
736   // Build lookup name
737   if (strcmp(type, "Ceed")) strncat(lookup_name, "Ceed", CEED_MAX_RESOURCE_LEN);
738   strncat(lookup_name, type, CEED_MAX_RESOURCE_LEN);
739   strncat(lookup_name, func_name, CEED_MAX_RESOURCE_LEN);
740 
741   // Find and use offset
742   for (CeedInt i = 0; ceed->f_offsets[i].func_name; i++) {
743     if (!strcmp(ceed->f_offsets[i].func_name, lookup_name)) {
744       size_t offset          = ceed->f_offsets[i].offset;
745       int (**fpointer)(void) = (int (**)(void))((char *)object + offset);  // *NOPAD*
746 
747       *fpointer = (int (*)(void))f;
748       return CEED_ERROR_SUCCESS;
749     }
750   }
751 
752   // LCOV_EXCL_START
753   return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Requested function '%s' was not found for CEED object '%s'", func_name, type);
754   // LCOV_EXCL_STOP
755 }
756 
757 /**
758   @brief Retrieve backend data for a `Ceed` context
759 
760   @param[in]  ceed `Ceed` context to retrieve data of
761   @param[out] data Address to save data to
762 
763   @return An error code: 0 - success, otherwise - failure
764 
765   @ref Backend
766 **/
767 int CeedGetData(Ceed ceed, void *data) {
768   *(void **)data = ceed->data;
769   return CEED_ERROR_SUCCESS;
770 }
771 
772 /**
773   @brief Set backend data for a `Ceed` context
774 
775   @param[in,out] ceed `Ceed` context to set data of
776   @param[in]     data Address of data to set
777 
778   @return An error code: 0 - success, otherwise - failure
779 
780   @ref Backend
781 **/
782 int CeedSetData(Ceed ceed, void *data) {
783   ceed->data = data;
784   return CEED_ERROR_SUCCESS;
785 }
786 
787 /**
788   @brief Increment the reference counter for a `Ceed` context
789 
790   @param[in,out] ceed `Ceed` context to increment the reference counter
791 
792   @return An error code: 0 - success, otherwise - failure
793 
794   @ref Backend
795 **/
796 int CeedReference(Ceed ceed) {
797   ceed->ref_count++;
798   return CEED_ERROR_SUCCESS;
799 }
800 
801 /**
802   @brief Computes the current memory usage of the work vectors in a `Ceed` context and prints to debug.abort
803 
804   @param[in]  ceed     `Ceed` context
805   @param[out] usage_mb Address of the variable where the MB of work vector usage will be stored
806 
807   @return An error code: 0 - success, otherwise - failure
808 
809   @ref Developer
810 **/
811 int CeedGetWorkVectorMemoryUsage(Ceed ceed, CeedScalar *usage_mb) {
812   if (!ceed->VectorCreate) {
813     Ceed delegate;
814 
815     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
816     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
817     CeedCall(CeedGetWorkVectorMemoryUsage(delegate, usage_mb));
818     CeedCall(CeedDestroy(&delegate));
819     return CEED_ERROR_SUCCESS;
820   }
821   *usage_mb = 0.0;
822   if (ceed->work_vectors) {
823     for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
824       CeedSize vec_len;
825       CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &vec_len));
826       *usage_mb += vec_len;
827     }
828     *usage_mb *= sizeof(CeedScalar) * 1e-6;
829     CeedDebug(ceed, "Resource {%s}: Work vectors memory usage: %" CeedInt_FMT " vectors, %g MB\n", ceed->resource, ceed->work_vectors->num_vecs,
830               *usage_mb);
831   }
832   return CEED_ERROR_SUCCESS;
833 }
834 
835 /**
836   @brief Clear inactive work vectors in a `Ceed` context below a minimum length.
837 
838   @param[in,out] ceed    `Ceed` context
839   @param[in]     min_len Minimum length of work vector to keep
840 
841   @return An error code: 0 - success, otherwise - failure
842 
843   @ref Backend
844 **/
845 int CeedClearWorkVectors(Ceed ceed, CeedSize min_len) {
846   if (!ceed->VectorCreate) {
847     Ceed delegate;
848 
849     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
850     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
851     CeedCall(CeedClearWorkVectors(delegate, min_len));
852     CeedCall(CeedDestroy(&delegate));
853     return CEED_ERROR_SUCCESS;
854   }
855   if (!ceed->work_vectors) return CEED_ERROR_SUCCESS;
856   for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
857     if (ceed->work_vectors->is_in_use[i]) continue;
858     CeedSize vec_len;
859     CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &vec_len));
860     if (vec_len < min_len) {
861       ceed->ref_count += 2;  // Note: increase ref_count to prevent Ceed destructor from triggering
862       CeedCall(CeedVectorDestroy(&ceed->work_vectors->vecs[i]));
863       ceed->ref_count -= 1;  // Note: restore ref_count
864       ceed->work_vectors->num_vecs--;
865       if (ceed->work_vectors->num_vecs > 0) {
866         ceed->work_vectors->vecs[i]                                 = ceed->work_vectors->vecs[ceed->work_vectors->num_vecs];
867         ceed->work_vectors->is_in_use[i]                            = ceed->work_vectors->is_in_use[ceed->work_vectors->num_vecs];
868         ceed->work_vectors->is_in_use[ceed->work_vectors->num_vecs] = false;
869         i--;
870       }
871     }
872   }
873   return CEED_ERROR_SUCCESS;
874 }
875 
876 /**
877   @brief Get a `CeedVector` for scratch work from a `Ceed` context.
878 
879   Note: This vector must be restored with @ref CeedRestoreWorkVector().
880 
881   @param[in]  ceed `Ceed` context
882   @param[in]  len  Minimum length of work vector
883   @param[out] vec  Address of the variable where `CeedVector` will be stored
884 
885   @return An error code: 0 - success, otherwise - failure
886 
887   @ref Backend
888 **/
889 int CeedGetWorkVector(Ceed ceed, CeedSize len, CeedVector *vec) {
890   CeedInt    i = 0;
891   CeedScalar usage_mb;
892 
893   if (!ceed->VectorCreate) {
894     Ceed delegate;
895 
896     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
897     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
898     CeedCall(CeedGetWorkVector(delegate, len, vec));
899     CeedCall(CeedDestroy(&delegate));
900     return CEED_ERROR_SUCCESS;
901   }
902 
903   if (!ceed->work_vectors) CeedCall(CeedWorkVectorsCreate(ceed));
904 
905   // Search for big enough work vector
906   for (i = 0; i < ceed->work_vectors->num_vecs; i++) {
907     if (!ceed->work_vectors->is_in_use[i]) {
908       CeedSize work_len;
909 
910       CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &work_len));
911       if (work_len >= len) break;
912     }
913   }
914   // Long enough vector was not found
915   if (i == ceed->work_vectors->num_vecs) {
916     if (ceed->work_vectors->max_vecs == 0) {
917       ceed->work_vectors->max_vecs = 1;
918       CeedCall(CeedCalloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->vecs));
919       CeedCall(CeedCalloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->is_in_use));
920     } else if (ceed->work_vectors->max_vecs == i) {
921       ceed->work_vectors->max_vecs *= 2;
922       CeedCall(CeedRealloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->vecs));
923       CeedCall(CeedRealloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->is_in_use));
924     }
925     ceed->work_vectors->num_vecs++;
926     CeedCallBackend(CeedVectorCreate(ceed, len, &ceed->work_vectors->vecs[i]));
927     ceed->ref_count--;  // Note: ref_count manipulation to prevent a ref-loop
928     if (ceed->is_debug) CeedGetWorkVectorMemoryUsage(ceed, &usage_mb);
929   }
930   // Return pointer to work vector
931   ceed->work_vectors->is_in_use[i] = true;
932   *vec                             = NULL;
933   CeedCall(CeedVectorReferenceCopy(ceed->work_vectors->vecs[i], vec));
934   ceed->ref_count++;  // Note: bump ref_count to account for external access
935   return CEED_ERROR_SUCCESS;
936 }
937 
938 /**
939   @brief Restore a `CeedVector` for scratch work from a `Ceed` context from @ref CeedGetWorkVector()
940 
941   @param[in]  ceed `Ceed` context
942   @param[out] vec  `CeedVector` to restore
943 
944   @return An error code: 0 - success, otherwise - failure
945 
946   @ref Backend
947 **/
948 int CeedRestoreWorkVector(Ceed ceed, CeedVector *vec) {
949   if (!ceed->VectorCreate) {
950     Ceed delegate;
951 
952     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
953     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
954     CeedCall(CeedRestoreWorkVector(delegate, vec));
955     CeedCall(CeedDestroy(&delegate));
956     return CEED_ERROR_SUCCESS;
957   }
958 
959   for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
960     if (*vec == ceed->work_vectors->vecs[i]) {
961       CeedCheck(ceed->work_vectors->is_in_use[i], ceed, CEED_ERROR_ACCESS, "Work vector %" CeedSize_FMT " was not checked out but is being returned");
962       CeedCall(CeedVectorDestroy(vec));
963       ceed->work_vectors->is_in_use[i] = false;
964       ceed->ref_count--;  // Note: reduce ref_count again to prevent a ref-loop
965       return CEED_ERROR_SUCCESS;
966     }
967   }
968   // LCOV_EXCL_START
969   return CeedError(ceed, CEED_ERROR_MAJOR, "vec was not checked out via CeedGetWorkVector()");
970   // LCOV_EXCL_STOP
971 }
972 
973 /**
974   @brief Retrieve list of additional JiT source roots from `Ceed` context.
975 
976   Note: The caller is responsible for restoring `jit_source_roots` with @ref CeedRestoreJitSourceRoots().
977 
978   @param[in]  ceed             `Ceed` context
979   @param[out] num_source_roots Number of JiT source directories
980   @param[out] jit_source_roots Absolute paths to additional JiT source directories
981 
982   @return An error code: 0 - success, otherwise - failure
983 
984   @ref Backend
985 **/
986 int CeedGetJitSourceRoots(Ceed ceed, CeedInt *num_source_roots, const char ***jit_source_roots) {
987   Ceed ceed_parent;
988 
989   CeedCall(CeedGetParent(ceed, &ceed_parent));
990   *num_source_roots = ceed_parent->num_jit_source_roots;
991   *jit_source_roots = (const char **)ceed_parent->jit_source_roots;
992   ceed_parent->num_jit_source_roots_readers++;
993   CeedCall(CeedDestroy(&ceed_parent));
994   return CEED_ERROR_SUCCESS;
995 }
996 
997 /**
998   @brief Restore list of additional JiT source roots from with @ref CeedGetJitSourceRoots()
999 
1000   @param[in]  ceed             `Ceed` context
1001   @param[out] jit_source_roots Absolute paths to additional JiT source directories
1002 
1003   @return An error code: 0 - success, otherwise - failure
1004 
1005   @ref Backend
1006 **/
1007 int CeedRestoreJitSourceRoots(Ceed ceed, const char ***jit_source_roots) {
1008   Ceed ceed_parent;
1009 
1010   CeedCall(CeedGetParent(ceed, &ceed_parent));
1011   *jit_source_roots = NULL;
1012   ceed_parent->num_jit_source_roots_readers--;
1013   CeedCall(CeedDestroy(&ceed_parent));
1014   return CEED_ERROR_SUCCESS;
1015 }
1016 
1017 /**
1018   @brief Retrieve list of additional JiT defines from `Ceed` context.
1019 
1020   Note: The caller is responsible for restoring `jit_defines` with @ref CeedRestoreJitDefines().
1021 
1022   @param[in]  ceed            `Ceed` context
1023   @param[out] num_jit_defines Number of JiT defines
1024   @param[out] jit_defines     Strings such as `foo=bar`, used as `-Dfoo=bar` in JiT
1025 
1026   @return An error code: 0 - success, otherwise - failure
1027 
1028   @ref Backend
1029 **/
1030 int CeedGetJitDefines(Ceed ceed, CeedInt *num_jit_defines, const char ***jit_defines) {
1031   Ceed ceed_parent;
1032 
1033   CeedCall(CeedGetParent(ceed, &ceed_parent));
1034   *num_jit_defines = ceed_parent->num_jit_defines;
1035   *jit_defines     = (const char **)ceed_parent->jit_defines;
1036   ceed_parent->num_jit_defines_readers++;
1037   CeedCall(CeedDestroy(&ceed_parent));
1038   return CEED_ERROR_SUCCESS;
1039 }
1040 
1041 /**
1042   @brief Restore list of additional JiT defines from with @ref CeedGetJitDefines()
1043 
1044   @param[in]  ceed        `Ceed` context
1045   @param[out] jit_defines String such as `foo=bar`, used as `-Dfoo=bar` in JiT
1046 
1047   @return An error code: 0 - success, otherwise - failure
1048 
1049   @ref Backend
1050 **/
1051 int CeedRestoreJitDefines(Ceed ceed, const char ***jit_defines) {
1052   Ceed ceed_parent;
1053 
1054   CeedCall(CeedGetParent(ceed, &ceed_parent));
1055   *jit_defines = NULL;
1056   ceed_parent->num_jit_defines_readers--;
1057   CeedCall(CeedDestroy(&ceed_parent));
1058   return CEED_ERROR_SUCCESS;
1059 }
1060 
1061 /// @}
1062 
1063 /// ----------------------------------------------------------------------------
1064 /// Ceed Public API
1065 /// ----------------------------------------------------------------------------
1066 /// @addtogroup CeedUser
1067 /// @{
1068 
1069 /**
1070   @brief Get the list of available resource names for `Ceed` contexts
1071 
1072   Note: The caller is responsible for `free()`ing the resources and priorities arrays, but should not `free()` the contents of the resources array.
1073 
1074   @param[out] n          Number of available resources
1075   @param[out] resources  List of available resource names
1076   @param[out] priorities Resource name prioritization values, lower is better
1077 
1078   @return An error code: 0 - success, otherwise - failure
1079 
1080   @ref User
1081 **/
1082 // LCOV_EXCL_START
1083 int CeedRegistryGetList(size_t *n, char ***const resources, CeedInt **priorities) {
1084   *n         = 0;
1085   *resources = malloc(num_backends * sizeof(**resources));
1086   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "malloc() failure");
1087   if (priorities) {
1088     *priorities = malloc(num_backends * sizeof(**priorities));
1089     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "malloc() failure");
1090   }
1091   for (size_t i = 0; i < num_backends; i++) {
1092     // Only report compiled backends
1093     if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) {
1094       *resources[i] = backends[i].prefix;
1095       if (priorities) *priorities[i] = backends[i].priority;
1096       *n += 1;
1097     }
1098   }
1099   CeedCheck(*n, NULL, CEED_ERROR_MAJOR, "No backends installed");
1100   *resources = realloc(*resources, *n * sizeof(**resources));
1101   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "realloc() failure");
1102   if (priorities) {
1103     *priorities = realloc(*priorities, *n * sizeof(**priorities));
1104     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "realloc() failure");
1105   }
1106   return CEED_ERROR_SUCCESS;
1107 }
1108 // LCOV_EXCL_STOP
1109 
1110 /**
1111   @brief Initialize a `Ceed` context to use the specified resource.
1112 
1113   Note: Prefixing the resource with "help:" (e.g. "help:/cpu/self") will result in @ref CeedInt() printing the current libCEED version number and a list of current available backend resources to `stderr`.
1114 
1115   @param[in]  resource Resource to use, e.g., "/cpu/self"
1116   @param[out] ceed     The library context
1117 
1118   @return An error code: 0 - success, otherwise - failure
1119 
1120   @ref User
1121 
1122   @sa CeedRegister() CeedDestroy()
1123 **/
1124 int CeedInit(const char *resource, Ceed *ceed) {
1125   size_t match_len = 0, match_index = UINT_MAX, match_priority = CEED_MAX_BACKEND_PRIORITY, priority;
1126 
1127   // Find matching backend
1128   CeedCheck(resource, NULL, CEED_ERROR_MAJOR, "No resource provided");
1129   CeedCall(CeedRegisterAll());
1130 
1131   // Check for help request
1132   const char *help_prefix = "help";
1133   size_t      match_help  = 0;
1134   while (match_help < 4 && resource[match_help] == help_prefix[match_help]) match_help++;
1135   if (match_help == 4) {
1136     fprintf(stderr, "libCEED version: %d.%d%d%s\n", CEED_VERSION_MAJOR, CEED_VERSION_MINOR, CEED_VERSION_PATCH,
1137             CEED_VERSION_RELEASE ? "" : "+development");
1138     fprintf(stderr, "Available backend resources:\n");
1139     for (size_t i = 0; i < num_backends; i++) {
1140       // Only report compiled backends
1141       if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) fprintf(stderr, "  %s\n", backends[i].prefix);
1142     }
1143     fflush(stderr);
1144     match_help = 5;  // Delineating character expected
1145   } else {
1146     match_help = 0;
1147   }
1148 
1149   // Find best match, computed as number of matching characters from requested resource stem
1150   size_t stem_length = 0;
1151   while (resource[stem_length + match_help] && resource[stem_length + match_help] != ':') stem_length++;
1152   for (size_t i = 0; i < num_backends; i++) {
1153     size_t      n      = 0;
1154     const char *prefix = backends[i].prefix;
1155     while (prefix[n] && prefix[n] == resource[n + match_help]) n++;
1156     priority = backends[i].priority;
1157     if (n > match_len || (n == match_len && match_priority > priority)) {
1158       match_len      = n;
1159       match_priority = priority;
1160       match_index    = i;
1161     }
1162   }
1163   // Using Levenshtein distance to find closest match
1164   if (match_len <= 1 || match_len != stem_length) {
1165     // LCOV_EXCL_START
1166     size_t lev_dis   = UINT_MAX;
1167     size_t lev_index = UINT_MAX, lev_priority = CEED_MAX_BACKEND_PRIORITY;
1168     for (size_t i = 0; i < num_backends; i++) {
1169       const char *prefix        = backends[i].prefix;
1170       size_t      prefix_length = strlen(backends[i].prefix);
1171       size_t      min_len       = (prefix_length < stem_length) ? prefix_length : stem_length;
1172       size_t      column[min_len + 1];
1173       for (size_t j = 0; j <= min_len; j++) column[j] = j;
1174       for (size_t j = 1; j <= min_len; j++) {
1175         column[0] = j;
1176         for (size_t k = 1, last_diag = j - 1; k <= min_len; k++) {
1177           size_t old_diag = column[k];
1178           size_t min_1    = (column[k] < column[k - 1]) ? column[k] + 1 : column[k - 1] + 1;
1179           size_t min_2    = last_diag + (resource[k - 1] == prefix[j - 1] ? 0 : 1);
1180           column[k]       = (min_1 < min_2) ? min_1 : min_2;
1181           last_diag       = old_diag;
1182         }
1183       }
1184       size_t n = column[min_len];
1185       priority = backends[i].priority;
1186       if (n < lev_dis || (n == lev_dis && lev_priority > priority)) {
1187         lev_dis      = n;
1188         lev_priority = priority;
1189         lev_index    = i;
1190       }
1191     }
1192     const char *prefix_lev = backends[lev_index].prefix;
1193     size_t      lev_length = 0;
1194     while (prefix_lev[lev_length] && prefix_lev[lev_length] != '\0') lev_length++;
1195     size_t m = (lev_length < stem_length) ? lev_length : stem_length;
1196     if (lev_dis + 1 >= m) return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s", resource);
1197     else return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s\nClosest match: %s", resource, backends[lev_index].prefix);
1198     // LCOV_EXCL_STOP
1199   }
1200 
1201   // Setup Ceed
1202   CeedCall(CeedCalloc(1, ceed));
1203   CeedCall(CeedCalloc(1, &(*ceed)->jit_source_roots));
1204   const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1205   if (!ceed_error_handler) ceed_error_handler = "abort";
1206   if (!strcmp(ceed_error_handler, "exit")) (*ceed)->Error = CeedErrorExit;
1207   else if (!strcmp(ceed_error_handler, "store")) (*ceed)->Error = CeedErrorStore;
1208   else (*ceed)->Error = CeedErrorAbort;
1209   memcpy((*ceed)->err_msg, "No error message stored", 24);
1210   (*ceed)->ref_count = 1;
1211   (*ceed)->data      = NULL;
1212 
1213   // Set lookup table
1214   FOffset f_offsets[] = {
1215       CEED_FTABLE_ENTRY(Ceed, Error),
1216       CEED_FTABLE_ENTRY(Ceed, SetStream),
1217       CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
1218       CEED_FTABLE_ENTRY(Ceed, Destroy),
1219       CEED_FTABLE_ENTRY(Ceed, VectorCreate),
1220       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
1221       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateAtPoints),
1222       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
1223       CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
1224       CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
1225       CEED_FTABLE_ENTRY(Ceed, BasisCreateHdiv),
1226       CEED_FTABLE_ENTRY(Ceed, BasisCreateHcurl),
1227       CEED_FTABLE_ENTRY(Ceed, TensorContractCreate),
1228       CEED_FTABLE_ENTRY(Ceed, QFunctionCreate),
1229       CEED_FTABLE_ENTRY(Ceed, QFunctionContextCreate),
1230       CEED_FTABLE_ENTRY(Ceed, OperatorCreate),
1231       CEED_FTABLE_ENTRY(Ceed, OperatorCreateAtPoints),
1232       CEED_FTABLE_ENTRY(Ceed, CompositeOperatorCreate),
1233       CEED_FTABLE_ENTRY(CeedVector, HasValidArray),
1234       CEED_FTABLE_ENTRY(CeedVector, HasBorrowedArrayOfType),
1235       CEED_FTABLE_ENTRY(CeedVector, CopyStrided),
1236       CEED_FTABLE_ENTRY(CeedVector, SetArray),
1237       CEED_FTABLE_ENTRY(CeedVector, TakeArray),
1238       CEED_FTABLE_ENTRY(CeedVector, SetValue),
1239       CEED_FTABLE_ENTRY(CeedVector, SetValueStrided),
1240       CEED_FTABLE_ENTRY(CeedVector, SyncArray),
1241       CEED_FTABLE_ENTRY(CeedVector, GetArray),
1242       CEED_FTABLE_ENTRY(CeedVector, GetArrayRead),
1243       CEED_FTABLE_ENTRY(CeedVector, GetArrayWrite),
1244       CEED_FTABLE_ENTRY(CeedVector, RestoreArray),
1245       CEED_FTABLE_ENTRY(CeedVector, RestoreArrayRead),
1246       CEED_FTABLE_ENTRY(CeedVector, Norm),
1247       CEED_FTABLE_ENTRY(CeedVector, Scale),
1248       CEED_FTABLE_ENTRY(CeedVector, AXPY),
1249       CEED_FTABLE_ENTRY(CeedVector, AXPBY),
1250       CEED_FTABLE_ENTRY(CeedVector, PointwiseMult),
1251       CEED_FTABLE_ENTRY(CeedVector, Reciprocal),
1252       CEED_FTABLE_ENTRY(CeedVector, Destroy),
1253       CEED_FTABLE_ENTRY(CeedElemRestriction, Apply),
1254       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnsigned),
1255       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnoriented),
1256       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyAtPointsInElement),
1257       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyBlock),
1258       CEED_FTABLE_ENTRY(CeedElemRestriction, GetOffsets),
1259       CEED_FTABLE_ENTRY(CeedElemRestriction, GetOrientations),
1260       CEED_FTABLE_ENTRY(CeedElemRestriction, GetCurlOrientations),
1261       CEED_FTABLE_ENTRY(CeedElemRestriction, GetAtPointsElementOffset),
1262       CEED_FTABLE_ENTRY(CeedElemRestriction, Destroy),
1263       CEED_FTABLE_ENTRY(CeedBasis, Apply),
1264       CEED_FTABLE_ENTRY(CeedBasis, ApplyAdd),
1265       CEED_FTABLE_ENTRY(CeedBasis, ApplyAtPoints),
1266       CEED_FTABLE_ENTRY(CeedBasis, ApplyAddAtPoints),
1267       CEED_FTABLE_ENTRY(CeedBasis, Destroy),
1268       CEED_FTABLE_ENTRY(CeedTensorContract, Apply),
1269       CEED_FTABLE_ENTRY(CeedTensorContract, Destroy),
1270       CEED_FTABLE_ENTRY(CeedQFunction, Apply),
1271       CEED_FTABLE_ENTRY(CeedQFunction, SetCUDAUserFunction),
1272       CEED_FTABLE_ENTRY(CeedQFunction, SetHIPUserFunction),
1273       CEED_FTABLE_ENTRY(CeedQFunction, Destroy),
1274       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasValidData),
1275       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasBorrowedDataOfType),
1276       CEED_FTABLE_ENTRY(CeedQFunctionContext, SetData),
1277       CEED_FTABLE_ENTRY(CeedQFunctionContext, TakeData),
1278       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetData),
1279       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetDataRead),
1280       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreData),
1281       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreDataRead),
1282       CEED_FTABLE_ENTRY(CeedQFunctionContext, DataDestroy),
1283       CEED_FTABLE_ENTRY(CeedQFunctionContext, Destroy),
1284       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunction),
1285       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunctionUpdate),
1286       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleDiagonal),
1287       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddDiagonal),
1288       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemblePointBlockDiagonal),
1289       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddPointBlockDiagonal),
1290       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSymbolic),
1291       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemble),
1292       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSingle),
1293       CEED_FTABLE_ENTRY(CeedOperator, CreateFDMElementInverse),
1294       CEED_FTABLE_ENTRY(CeedOperator, Apply),
1295       CEED_FTABLE_ENTRY(CeedOperator, ApplyComposite),
1296       CEED_FTABLE_ENTRY(CeedOperator, ApplyAdd),
1297       CEED_FTABLE_ENTRY(CeedOperator, ApplyAddComposite),
1298       CEED_FTABLE_ENTRY(CeedOperator, ApplyJacobian),
1299       CEED_FTABLE_ENTRY(CeedOperator, Destroy),
1300       {NULL, 0}  // End of lookup table - used in SetBackendFunction loop
1301   };
1302 
1303   CeedCall(CeedCalloc(sizeof(f_offsets), &(*ceed)->f_offsets));
1304   memcpy((*ceed)->f_offsets, f_offsets, sizeof(f_offsets));
1305 
1306   // Set fallback for advanced CeedOperator functions
1307   const char fallback_resource[] = "";
1308   CeedCall(CeedSetOperatorFallbackResource(*ceed, fallback_resource));
1309 
1310   // Record env variables CEED_DEBUG or DBG
1311   (*ceed)->is_debug = getenv("CEED_DEBUG") || getenv("DEBUG") || getenv("DBG");
1312 
1313   // Copy resource prefix, if backend setup successful
1314   CeedCall(CeedStringAllocCopy(backends[match_index].prefix, (char **)&(*ceed)->resource));
1315 
1316   // Set default JiT source root
1317   // Note: there will always be the default root for every Ceed but all additional paths are added to the top-most parent
1318   CeedCall(CeedAddJitSourceRoot(*ceed, (char *)CeedJitSourceRootDefault));
1319 
1320   // Backend specific setup
1321   CeedCall(backends[match_index].init(&resource[match_help], *ceed));
1322   return CEED_ERROR_SUCCESS;
1323 }
1324 
1325 /**
1326   @brief Set the GPU stream for a `Ceed` context
1327 
1328   @param[in,out] ceed   `Ceed` context to set the stream
1329   @param[in]     handle Handle to GPU stream
1330 
1331   @return An error code: 0 - success, otherwise - failure
1332 
1333   @ref User
1334 **/
1335 int CeedSetStream(Ceed ceed, void *handle) {
1336   CeedCheck(handle, ceed, CEED_ERROR_INCOMPATIBLE, "Stream handle must be non-NULL");
1337   if (ceed->SetStream) {
1338     CeedCall(ceed->SetStream(ceed, handle));
1339   } else {
1340     Ceed delegate;
1341     CeedCall(CeedGetDelegate(ceed, &delegate));
1342 
1343     if (delegate) CeedCall(CeedSetStream(delegate, handle));
1344     else return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Backend does not support setting stream");
1345     CeedCall(CeedDestroy(&delegate));
1346   }
1347   return CEED_ERROR_SUCCESS;
1348 }
1349 
1350 /**
1351   @brief Copy the pointer to a `Ceed` context.
1352 
1353   Both pointers should be destroyed with @ref CeedDestroy().
1354 
1355   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.
1356         This `Ceed` context will be destroyed if `*ceed_copy` is the only reference to this `Ceed` context.
1357 
1358   @param[in]     ceed      `Ceed` context to copy reference to
1359   @param[in,out] ceed_copy Variable to store copied reference
1360 
1361   @return An error code: 0 - success, otherwise - failure
1362 
1363   @ref User
1364 **/
1365 int CeedReferenceCopy(Ceed ceed, Ceed *ceed_copy) {
1366   CeedCall(CeedReference(ceed));
1367   CeedCall(CeedDestroy(ceed_copy));
1368   *ceed_copy = ceed;
1369   return CEED_ERROR_SUCCESS;
1370 }
1371 
1372 /**
1373   @brief Get the full resource name for a `Ceed` context
1374 
1375   @param[in]  ceed     `Ceed` context to get resource name of
1376   @param[out] resource Variable to store resource name
1377 
1378   @return An error code: 0 - success, otherwise - failure
1379 
1380   @ref User
1381 **/
1382 int CeedGetResource(Ceed ceed, const char **resource) {
1383   *resource = (const char *)ceed->resource;
1384   return CEED_ERROR_SUCCESS;
1385 }
1386 
1387 /**
1388   @brief Return `Ceed` context preferred memory type
1389 
1390   @param[in]  ceed     `Ceed` context to get preferred memory type of
1391   @param[out] mem_type Address to save preferred memory type to
1392 
1393   @return An error code: 0 - success, otherwise - failure
1394 
1395   @ref User
1396 **/
1397 int CeedGetPreferredMemType(Ceed ceed, CeedMemType *mem_type) {
1398   if (ceed->GetPreferredMemType) {
1399     CeedCall(ceed->GetPreferredMemType(mem_type));
1400   } else {
1401     Ceed delegate;
1402     CeedCall(CeedGetDelegate(ceed, &delegate));
1403 
1404     if (delegate) {
1405       CeedCall(CeedGetPreferredMemType(delegate, mem_type));
1406     } else {
1407       *mem_type = CEED_MEM_HOST;
1408     }
1409     CeedCall(CeedDestroy(&delegate));
1410   }
1411   return CEED_ERROR_SUCCESS;
1412 }
1413 
1414 /**
1415   @brief Get deterministic status of `Ceed` context
1416 
1417   @param[in]  ceed             `Ceed` context
1418   @param[out] is_deterministic Variable to store deterministic status
1419 
1420   @return An error code: 0 - success, otherwise - failure
1421 
1422   @ref User
1423 **/
1424 int CeedIsDeterministic(Ceed ceed, bool *is_deterministic) {
1425   *is_deterministic = ceed->is_deterministic;
1426   return CEED_ERROR_SUCCESS;
1427 }
1428 
1429 /**
1430   @brief Set additional JiT source root for `Ceed` context
1431 
1432   @param[in,out] ceed            `Ceed` context
1433   @param[in]     jit_source_root Absolute path to additional JiT source directory
1434 
1435   @return An error code: 0 - success, otherwise - failure
1436 
1437   @ref User
1438 **/
1439 int CeedAddJitSourceRoot(Ceed ceed, const char *jit_source_root) {
1440   Ceed ceed_parent;
1441 
1442   CeedCall(CeedGetParent(ceed, &ceed_parent));
1443   CeedCheck(!ceed_parent->num_jit_source_roots_readers, ceed, CEED_ERROR_ACCESS, "Cannot add JiT source root, read access has not been restored");
1444 
1445   CeedInt index       = ceed_parent->num_jit_source_roots;
1446   size_t  path_length = strlen(jit_source_root);
1447 
1448   if (ceed_parent->num_jit_source_roots == ceed_parent->max_jit_source_roots) {
1449     if (ceed_parent->max_jit_source_roots == 0) ceed_parent->max_jit_source_roots = 1;
1450     ceed_parent->max_jit_source_roots *= 2;
1451     CeedCall(CeedRealloc(ceed_parent->max_jit_source_roots, &ceed_parent->jit_source_roots));
1452   }
1453   CeedCall(CeedCalloc(path_length + 1, &ceed_parent->jit_source_roots[index]));
1454   memcpy(ceed_parent->jit_source_roots[index], jit_source_root, path_length);
1455   ceed_parent->num_jit_source_roots++;
1456   CeedCall(CeedDestroy(&ceed_parent));
1457   return CEED_ERROR_SUCCESS;
1458 }
1459 
1460 /**
1461   @brief Set additional JiT compiler define for `Ceed` context
1462 
1463   @param[in,out] ceed       `Ceed` context
1464   @param[in]     jit_define String such as `foo=bar`, used as `-Dfoo=bar` in JiT
1465 
1466   @return An error code: 0 - success, otherwise - failure
1467 
1468   @ref User
1469 **/
1470 int CeedAddJitDefine(Ceed ceed, const char *jit_define) {
1471   Ceed ceed_parent;
1472 
1473   CeedCall(CeedGetParent(ceed, &ceed_parent));
1474   CeedCheck(!ceed_parent->num_jit_defines_readers, ceed, CEED_ERROR_ACCESS, "Cannot add JiT define, read access has not been restored");
1475 
1476   CeedInt index         = ceed_parent->num_jit_defines;
1477   size_t  define_length = strlen(jit_define);
1478 
1479   if (ceed_parent->num_jit_defines == ceed_parent->max_jit_defines) {
1480     if (ceed_parent->max_jit_defines == 0) ceed_parent->max_jit_defines = 1;
1481     ceed_parent->max_jit_defines *= 2;
1482     CeedCall(CeedRealloc(ceed_parent->max_jit_defines, &ceed_parent->jit_defines));
1483   }
1484   CeedCall(CeedCalloc(define_length + 1, &ceed_parent->jit_defines[index]));
1485   memcpy(ceed_parent->jit_defines[index], jit_define, define_length);
1486   ceed_parent->num_jit_defines++;
1487   CeedCall(CeedDestroy(&ceed_parent));
1488   return CEED_ERROR_SUCCESS;
1489 }
1490 
1491 /**
1492   @brief View a `Ceed`
1493 
1494   @param[in] ceed   `Ceed` to view
1495   @param[in] stream Filestream to write to
1496 
1497   @return An error code: 0 - success, otherwise - failure
1498 
1499   @ref User
1500 **/
1501 int CeedView(Ceed ceed, FILE *stream) {
1502   CeedMemType mem_type;
1503 
1504   CeedCall(CeedGetPreferredMemType(ceed, &mem_type));
1505 
1506   fprintf(stream,
1507           "Ceed\n"
1508           "  Ceed Resource: %s\n"
1509           "  Preferred MemType: %s\n",
1510           ceed->resource, CeedMemTypes[mem_type]);
1511   return CEED_ERROR_SUCCESS;
1512 }
1513 
1514 /**
1515   @brief Destroy a `Ceed`
1516 
1517   @param[in,out] ceed Address of `Ceed` context to destroy
1518 
1519   @return An error code: 0 - success, otherwise - failure
1520 
1521   @ref User
1522 **/
1523 int CeedDestroy(Ceed *ceed) {
1524   if (!*ceed || --(*ceed)->ref_count > 0) {
1525     *ceed = NULL;
1526     return CEED_ERROR_SUCCESS;
1527   }
1528 
1529   CeedCheck(!(*ceed)->num_jit_source_roots_readers, *ceed, CEED_ERROR_ACCESS,
1530             "Cannot destroy ceed context, read access for JiT source roots has been granted");
1531   CeedCheck(!(*ceed)->num_jit_defines_readers, *ceed, CEED_ERROR_ACCESS, "Cannot add JiT source root, read access for JiT defines has been granted");
1532 
1533   if ((*ceed)->delegate) CeedCall(CeedDestroy(&(*ceed)->delegate));
1534 
1535   if ((*ceed)->obj_delegate_count > 0) {
1536     for (CeedInt i = 0; i < (*ceed)->obj_delegate_count; i++) {
1537       CeedCall(CeedDestroy(&((*ceed)->obj_delegates[i].delegate)));
1538       CeedCall(CeedFree(&(*ceed)->obj_delegates[i].obj_name));
1539     }
1540     CeedCall(CeedFree(&(*ceed)->obj_delegates));
1541   }
1542 
1543   if ((*ceed)->Destroy) CeedCall((*ceed)->Destroy(*ceed));
1544 
1545   for (CeedInt i = 0; i < (*ceed)->num_jit_source_roots; i++) {
1546     CeedCall(CeedFree(&(*ceed)->jit_source_roots[i]));
1547   }
1548   CeedCall(CeedFree(&(*ceed)->jit_source_roots));
1549 
1550   for (CeedInt i = 0; i < (*ceed)->num_jit_defines; i++) {
1551     CeedCall(CeedFree(&(*ceed)->jit_defines[i]));
1552   }
1553   CeedCall(CeedFree(&(*ceed)->jit_defines));
1554 
1555   CeedCall(CeedFree(&(*ceed)->f_offsets));
1556   CeedCall(CeedFree(&(*ceed)->resource));
1557   CeedCall(CeedDestroy(&(*ceed)->op_fallback_ceed));
1558   CeedCall(CeedFree(&(*ceed)->op_fallback_resource));
1559   CeedCall(CeedWorkVectorsDestroy(*ceed));
1560   CeedCall(CeedFree(ceed));
1561   return CEED_ERROR_SUCCESS;
1562 }
1563 
1564 // LCOV_EXCL_START
1565 const char *CeedErrorFormat(Ceed ceed, const char *format, va_list *args) {
1566   if (ceed->parent) return CeedErrorFormat(ceed->parent, format, args);
1567   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1568   vsnprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, format, *args);  // NOLINT
1569   return ceed->err_msg;
1570 }
1571 // LCOV_EXCL_STOP
1572 
1573 /**
1574   @brief Error handling implementation; use @ref CeedError() instead.
1575 
1576   @return An error code: 0 - success, otherwise - failure
1577 
1578   @ref Developer
1579 **/
1580 int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func, int ecode, const char *format, ...) {
1581   va_list args;
1582   int     ret_val;
1583 
1584   va_start(args, format);
1585   if (ceed) {
1586     ret_val = ceed->Error(ceed, filename, lineno, func, ecode, format, &args);
1587   } else {
1588     // LCOV_EXCL_START
1589     const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1590     if (!ceed_error_handler) ceed_error_handler = "abort";
1591     if (!strcmp(ceed_error_handler, "return")) {
1592       ret_val = CeedErrorReturn(ceed, filename, lineno, func, ecode, format, &args);
1593     } else {
1594       // This function will not return
1595       ret_val = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, &args);
1596     }
1597   }
1598   va_end(args);
1599   return ret_val;
1600   // LCOV_EXCL_STOP
1601 }
1602 
1603 /**
1604   @brief Error handler that returns without printing anything.
1605 
1606   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1607 
1608   @return An error code: 0 - success, otherwise - failure
1609 
1610   @ref Developer
1611 **/
1612 // LCOV_EXCL_START
1613 int CeedErrorReturn(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1614   return err_code;
1615 }
1616 // LCOV_EXCL_STOP
1617 
1618 /**
1619   @brief Error handler that stores the error message for future use and returns the error.
1620 
1621   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1622 
1623   @return An error code: 0 - success, otherwise - failure
1624 
1625   @ref Developer
1626 **/
1627 // LCOV_EXCL_START
1628 int CeedErrorStore(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1629   if (ceed->parent) return CeedErrorStore(ceed->parent, filename, line_no, func, err_code, format, args);
1630 
1631   // Build message
1632   int len = snprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, "%s:%d in %s(): ", filename, line_no, func);
1633   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1634   vsnprintf(ceed->err_msg + len, CEED_MAX_RESOURCE_LEN - len, format, *args);  // NOLINT
1635   return err_code;
1636 }
1637 // LCOV_EXCL_STOP
1638 
1639 /**
1640   @brief Error handler that prints to `stderr` and aborts
1641 
1642   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1643 
1644   @return An error code: 0 - success, otherwise - failure
1645 
1646   @ref Developer
1647 **/
1648 // LCOV_EXCL_START
1649 int CeedErrorAbort(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1650   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1651   vfprintf(stderr, format, *args);
1652   fprintf(stderr, "\n");
1653   abort();
1654   return err_code;
1655 }
1656 // LCOV_EXCL_STOP
1657 
1658 /**
1659   @brief Error handler that prints to `stderr` and exits.
1660 
1661   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1662 
1663   In contrast to @ref CeedErrorAbort(), this exits without a signal, so `atexit()` handlers (e.g., as used by gcov) are run.
1664 
1665   @return An error code: 0 - success, otherwise - failure
1666 
1667   @ref Developer
1668 **/
1669 int CeedErrorExit(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1670   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1671   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1672   vfprintf(stderr, format, *args);  // NOLINT
1673   fprintf(stderr, "\n");
1674   exit(err_code);
1675   return err_code;
1676 }
1677 
1678 /**
1679   @brief Set error handler
1680 
1681   A default error handler is set in @ref CeedInit().
1682   Use this function to change the error handler to @ref CeedErrorReturn(), @ref CeedErrorAbort(), or a user-defined error handler.
1683 
1684   @return An error code: 0 - success, otherwise - failure
1685 
1686   @ref Developer
1687 **/
1688 int CeedSetErrorHandler(Ceed ceed, CeedErrorHandler handler) {
1689   ceed->Error = handler;
1690   if (ceed->delegate) CeedSetErrorHandler(ceed->delegate, handler);
1691   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) CeedSetErrorHandler(ceed->obj_delegates[i].delegate, handler);
1692   return CEED_ERROR_SUCCESS;
1693 }
1694 
1695 /**
1696   @brief Get error message
1697 
1698   The error message is only stored when using the error handler @ref CeedErrorStore()
1699 
1700   @param[in]  ceed    `Ceed` context to retrieve error message
1701   @param[out] err_msg Char pointer to hold error message
1702 
1703   @return An error code: 0 - success, otherwise - failure
1704 
1705   @ref Developer
1706 **/
1707 int CeedGetErrorMessage(Ceed ceed, const char **err_msg) {
1708   if (ceed->parent) return CeedGetErrorMessage(ceed->parent, err_msg);
1709   *err_msg = ceed->err_msg;
1710   return CEED_ERROR_SUCCESS;
1711 }
1712 
1713 /**
1714   @brief Restore error message.
1715 
1716   The error message is only stored when using the error handler @ref CeedErrorStore().
1717 
1718   @param[in]  ceed    `Ceed` context to restore error message
1719   @param[out] err_msg Char pointer that holds error message
1720 
1721   @return An error code: 0 - success, otherwise - failure
1722 
1723   @ref Developer
1724 **/
1725 int CeedResetErrorMessage(Ceed ceed, const char **err_msg) {
1726   if (ceed->parent) return CeedResetErrorMessage(ceed->parent, err_msg);
1727   *err_msg = NULL;
1728   memcpy(ceed->err_msg, "No error message stored", 24);
1729   return CEED_ERROR_SUCCESS;
1730 }
1731 
1732 /**
1733   @brief Get libCEED library version information.
1734 
1735   libCEED version numbers have the form major.minor.patch.
1736   Non-release versions may contain unstable interfaces.
1737 
1738   @param[out] major   Major version of the library
1739   @param[out] minor   Minor version of the library
1740   @param[out] patch   Patch (subminor) version of the library
1741   @param[out] release True for releases; false for development branches
1742 
1743   The caller may pass `NULL` for any arguments that are not needed.
1744 
1745   @return An error code: 0 - success, otherwise - failure
1746 
1747   @ref Developer
1748 
1749   @sa CEED_VERSION_GE() CeedGetGitVersion() CeedGetBuildConfiguration()
1750 */
1751 int CeedGetVersion(int *major, int *minor, int *patch, bool *release) {
1752   if (major) *major = CEED_VERSION_MAJOR;
1753   if (minor) *minor = CEED_VERSION_MINOR;
1754   if (patch) *patch = CEED_VERSION_PATCH;
1755   if (release) *release = CEED_VERSION_RELEASE;
1756   return CEED_ERROR_SUCCESS;
1757 }
1758 
1759 /**
1760   @brief Get libCEED scalar type, such as F64 or F32
1761 
1762   @param[out] scalar_type Type of libCEED scalars
1763 
1764   @return An error code: 0 - success, otherwise - failure
1765 
1766   @ref Developer
1767 */
1768 int CeedGetScalarType(CeedScalarType *scalar_type) {
1769   *scalar_type = CEED_SCALAR_TYPE;
1770   return CEED_ERROR_SUCCESS;
1771 }
1772 
1773 /// @}
1774