xref: /libCEED/interface/ceed.c (revision 6d7116d72e016bae92b657063812cb029c7e7da4)
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, "---------- CeedOperator Fallback ----------\n");
654     CeedDebug(ceed, "Getting fallback from %s to %s\n", ceed->resource, ceed->op_fallback_resource);
655   }
656 
657   // Create fallback Ceed if uninitalized
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->op_fallback_parent = ceed;
667     fallback_ceed->Error              = ceed->Error;
668     ceed->op_fallback_ceed            = fallback_ceed;
669     {
670       const char **jit_source_roots;
671       CeedInt      num_jit_source_roots = 0;
672 
673       CeedCall(CeedGetJitSourceRoots(ceed, &num_jit_source_roots, &jit_source_roots));
674       for (CeedInt i = 0; i < num_jit_source_roots; i++) {
675         CeedCall(CeedAddJitSourceRoot(fallback_ceed, jit_source_roots[i]));
676       }
677       CeedCall(CeedRestoreJitSourceRoots(ceed, &jit_source_roots));
678     }
679     {
680       const char **jit_defines;
681       CeedInt      num_jit_defines = 0;
682 
683       CeedCall(CeedGetJitDefines(ceed, &num_jit_defines, &jit_defines));
684       for (CeedInt i = 0; i < num_jit_defines; i++) {
685         CeedCall(CeedAddJitSourceRoot(fallback_ceed, jit_defines[i]));
686       }
687       CeedCall(CeedRestoreJitDefines(ceed, &jit_defines));
688     }
689   }
690   *fallback_ceed = NULL;
691   if (ceed->op_fallback_ceed) CeedCall(CeedReferenceCopy(ceed->op_fallback_ceed, fallback_ceed));
692   return CEED_ERROR_SUCCESS;
693 }
694 
695 /**
696   @brief Set the fallback resource for `CeedOperator`.
697 
698   The current resource, if any, is freed by calling this function.
699   This string is freed upon the destruction of the `Ceed` context.
700 
701   @param[in,out] ceed     `Ceed` context
702   @param[in]     resource Fallback resource to set
703 
704   @return An error code: 0 - success, otherwise - failure
705 
706   @ref Backend
707 **/
708 int CeedSetOperatorFallbackResource(Ceed ceed, const char *resource) {
709   // Free old
710   CeedCall(CeedFree(&ceed->op_fallback_resource));
711 
712   // Set new
713   CeedCall(CeedStringAllocCopy(resource, (char **)&ceed->op_fallback_resource));
714 
715   // Check validity
716   ceed->has_valid_op_fallback_resource = ceed->op_fallback_resource && ceed->resource && strcmp(ceed->op_fallback_resource, ceed->resource);
717   return CEED_ERROR_SUCCESS;
718 }
719 
720 /**
721   @brief Flag `Ceed` context as deterministic
722 
723   @param[in]  ceed             `Ceed` to flag as deterministic
724   @param[out] is_deterministic Deterministic status to set
725 
726   @return An error code: 0 - success, otherwise - failure
727 
728   @ref Backend
729 **/
730 int CeedSetDeterministic(Ceed ceed, bool is_deterministic) {
731   ceed->is_deterministic = is_deterministic;
732   return CEED_ERROR_SUCCESS;
733 }
734 
735 /**
736   @brief Set a backend function.
737 
738   This function is used for a backend to set the function associated with the Ceed objects.
739   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().
740   Note, the prefix 'Ceed' is not required for the object type ("Basis" vs "CeedBasis").
741 
742   @param[in]  ceed      `Ceed` context for error handling
743   @param[in]  type      Type of Ceed object to set function for
744   @param[out] object    Ceed object to set function for
745   @param[in]  func_name Name of function to set
746   @param[in]  f         Function to set
747 
748   @return An error code: 0 - success, otherwise - failure
749 
750   @ref Backend
751 **/
752 int CeedSetBackendFunctionImpl(Ceed ceed, const char *type, void *object, const char *func_name, void (*f)(void)) {
753   char lookup_name[CEED_MAX_RESOURCE_LEN + 1] = "";
754 
755   // Build lookup name
756   if (strcmp(type, "Ceed")) strncat(lookup_name, "Ceed", CEED_MAX_RESOURCE_LEN);
757   strncat(lookup_name, type, CEED_MAX_RESOURCE_LEN);
758   strncat(lookup_name, func_name, CEED_MAX_RESOURCE_LEN);
759 
760   // Find and use offset
761   for (CeedInt i = 0; ceed->f_offsets[i].func_name; i++) {
762     if (!strcmp(ceed->f_offsets[i].func_name, lookup_name)) {
763       size_t offset          = ceed->f_offsets[i].offset;
764       int (**fpointer)(void) = (int (**)(void))((char *)object + offset);  // *NOPAD*
765 
766       *fpointer = (int (*)(void))f;
767       return CEED_ERROR_SUCCESS;
768     }
769   }
770 
771   // LCOV_EXCL_START
772   return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Requested function '%s' was not found for CEED object '%s'", func_name, type);
773   // LCOV_EXCL_STOP
774 }
775 
776 /**
777   @brief Retrieve backend data for a `Ceed` context
778 
779   @param[in]  ceed `Ceed` context to retrieve data of
780   @param[out] data Address to save data to
781 
782   @return An error code: 0 - success, otherwise - failure
783 
784   @ref Backend
785 **/
786 int CeedGetData(Ceed ceed, void *data) {
787   *(void **)data = ceed->data;
788   return CEED_ERROR_SUCCESS;
789 }
790 
791 /**
792   @brief Set backend data for a `Ceed` context
793 
794   @param[in,out] ceed `Ceed` context to set data of
795   @param[in]     data Address of data to set
796 
797   @return An error code: 0 - success, otherwise - failure
798 
799   @ref Backend
800 **/
801 int CeedSetData(Ceed ceed, void *data) {
802   ceed->data = data;
803   return CEED_ERROR_SUCCESS;
804 }
805 
806 /**
807   @brief Increment the reference counter for a `Ceed` context
808 
809   @param[in,out] ceed `Ceed` context to increment the reference counter
810 
811   @return An error code: 0 - success, otherwise - failure
812 
813   @ref Backend
814 **/
815 int CeedReference(Ceed ceed) {
816   ceed->ref_count++;
817   return CEED_ERROR_SUCCESS;
818 }
819 
820 /**
821   @brief Computes the current memory usage of the work vectors in a `Ceed` context and prints to debug.abort
822 
823   @param[in]  ceed     `Ceed` context
824   @param[out] usage_mb Address of the variable where the MB of work vector usage will be stored
825 
826   @return An error code: 0 - success, otherwise - failure
827 
828   @ref Developer
829 **/
830 int CeedGetWorkVectorMemoryUsage(Ceed ceed, CeedScalar *usage_mb) {
831   if (!ceed->VectorCreate) {
832     Ceed delegate;
833 
834     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
835     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
836     CeedCall(CeedGetWorkVectorMemoryUsage(delegate, usage_mb));
837     CeedCall(CeedDestroy(&delegate));
838     return CEED_ERROR_SUCCESS;
839   }
840   *usage_mb = 0.0;
841   if (ceed->work_vectors) {
842     for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
843       CeedSize vec_len;
844       CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &vec_len));
845       *usage_mb += vec_len;
846     }
847     *usage_mb *= sizeof(CeedScalar) * 1e-6;
848     CeedDebug(ceed, "Resource {%s}: Work vectors memory usage: %" CeedInt_FMT " vectors, %g MB\n", ceed->resource, ceed->work_vectors->num_vecs,
849               *usage_mb);
850   }
851   return CEED_ERROR_SUCCESS;
852 }
853 
854 /**
855   @brief Clear inactive work vectors in a `Ceed` context below a minimum length.
856 
857   @param[in,out] ceed    `Ceed` context
858   @param[in]     min_len Minimum length of work vector to keep
859 
860   @return An error code: 0 - success, otherwise - failure
861 
862   @ref Backend
863 **/
864 int CeedClearWorkVectors(Ceed ceed, CeedSize min_len) {
865   if (!ceed->VectorCreate) {
866     Ceed delegate;
867 
868     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
869     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
870     CeedCall(CeedClearWorkVectors(delegate, min_len));
871     CeedCall(CeedDestroy(&delegate));
872     return CEED_ERROR_SUCCESS;
873   }
874   if (!ceed->work_vectors) return CEED_ERROR_SUCCESS;
875   for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
876     if (ceed->work_vectors->is_in_use[i]) continue;
877     CeedSize vec_len;
878     CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &vec_len));
879     if (vec_len < min_len) {
880       ceed->ref_count += 2;  // Note: increase ref_count to prevent Ceed destructor from triggering
881       CeedCall(CeedVectorDestroy(&ceed->work_vectors->vecs[i]));
882       ceed->ref_count -= 1;  // Note: restore ref_count
883       ceed->work_vectors->num_vecs--;
884       if (ceed->work_vectors->num_vecs > 0) {
885         ceed->work_vectors->vecs[i]                                 = ceed->work_vectors->vecs[ceed->work_vectors->num_vecs];
886         ceed->work_vectors->is_in_use[i]                            = ceed->work_vectors->is_in_use[ceed->work_vectors->num_vecs];
887         ceed->work_vectors->is_in_use[ceed->work_vectors->num_vecs] = false;
888         i--;
889       }
890     }
891   }
892   return CEED_ERROR_SUCCESS;
893 }
894 
895 /**
896   @brief Get a `CeedVector` for scratch work from a `Ceed` context.
897 
898   Note: This vector must be restored with @ref CeedRestoreWorkVector().
899 
900   @param[in]  ceed `Ceed` context
901   @param[in]  len  Minimum length of work vector
902   @param[out] vec  Address of the variable where `CeedVector` will be stored
903 
904   @return An error code: 0 - success, otherwise - failure
905 
906   @ref Backend
907 **/
908 int CeedGetWorkVector(Ceed ceed, CeedSize len, CeedVector *vec) {
909   CeedInt    i = 0;
910   CeedScalar usage_mb;
911 
912   if (!ceed->VectorCreate) {
913     Ceed delegate;
914 
915     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
916     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
917     CeedCall(CeedGetWorkVector(delegate, len, vec));
918     CeedCall(CeedDestroy(&delegate));
919     return CEED_ERROR_SUCCESS;
920   }
921 
922   if (!ceed->work_vectors) CeedCall(CeedWorkVectorsCreate(ceed));
923 
924   // Search for big enough work vector
925   for (i = 0; i < ceed->work_vectors->num_vecs; i++) {
926     if (!ceed->work_vectors->is_in_use[i]) {
927       CeedSize work_len;
928 
929       CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &work_len));
930       if (work_len >= len) break;
931     }
932   }
933   // Long enough vector was not found
934   if (i == ceed->work_vectors->num_vecs) {
935     if (ceed->work_vectors->max_vecs == 0) {
936       ceed->work_vectors->max_vecs = 1;
937       CeedCall(CeedCalloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->vecs));
938       CeedCall(CeedCalloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->is_in_use));
939     } else if (ceed->work_vectors->max_vecs == i) {
940       ceed->work_vectors->max_vecs *= 2;
941       CeedCall(CeedRealloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->vecs));
942       CeedCall(CeedRealloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->is_in_use));
943     }
944     ceed->work_vectors->num_vecs++;
945     CeedCallBackend(CeedVectorCreate(ceed, len, &ceed->work_vectors->vecs[i]));
946     ceed->ref_count--;  // Note: ref_count manipulation to prevent a ref-loop
947     if (ceed->is_debug) CeedGetWorkVectorMemoryUsage(ceed, &usage_mb);
948   }
949   // Return pointer to work vector
950   ceed->work_vectors->is_in_use[i] = true;
951   *vec                             = NULL;
952   CeedCall(CeedVectorReferenceCopy(ceed->work_vectors->vecs[i], vec));
953   ceed->ref_count++;  // Note: bump ref_count to account for external access
954   return CEED_ERROR_SUCCESS;
955 }
956 
957 /**
958   @brief Restore a `CeedVector` for scratch work from a `Ceed` context from @ref CeedGetWorkVector()
959 
960   @param[in]  ceed `Ceed` context
961   @param[out] vec  `CeedVector` to restore
962 
963   @return An error code: 0 - success, otherwise - failure
964 
965   @ref Backend
966 **/
967 int CeedRestoreWorkVector(Ceed ceed, CeedVector *vec) {
968   if (!ceed->VectorCreate) {
969     Ceed delegate;
970 
971     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
972     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
973     CeedCall(CeedRestoreWorkVector(delegate, vec));
974     CeedCall(CeedDestroy(&delegate));
975     return CEED_ERROR_SUCCESS;
976   }
977 
978   for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
979     if (*vec == ceed->work_vectors->vecs[i]) {
980       CeedCheck(ceed->work_vectors->is_in_use[i], ceed, CEED_ERROR_ACCESS, "Work vector %" CeedSize_FMT " was not checked out but is being returned");
981       CeedCall(CeedVectorDestroy(vec));
982       ceed->work_vectors->is_in_use[i] = false;
983       ceed->ref_count--;  // Note: reduce ref_count again to prevent a ref-loop
984       return CEED_ERROR_SUCCESS;
985     }
986   }
987   // LCOV_EXCL_START
988   return CeedError(ceed, CEED_ERROR_MAJOR, "vec was not checked out via CeedGetWorkVector()");
989   // LCOV_EXCL_STOP
990 }
991 
992 /**
993   @brief Retrieve list of additional JiT source roots from `Ceed` context.
994 
995   Note: The caller is responsible for restoring `jit_source_roots` with @ref CeedRestoreJitSourceRoots().
996 
997   @param[in]  ceed             `Ceed` context
998   @param[out] num_source_roots Number of JiT source directories
999   @param[out] jit_source_roots Absolute paths to additional JiT source directories
1000 
1001   @return An error code: 0 - success, otherwise - failure
1002 
1003   @ref Backend
1004 **/
1005 int CeedGetJitSourceRoots(Ceed ceed, CeedInt *num_source_roots, const char ***jit_source_roots) {
1006   Ceed ceed_parent;
1007 
1008   CeedCall(CeedGetParent(ceed, &ceed_parent));
1009   *num_source_roots = ceed_parent->num_jit_source_roots;
1010   *jit_source_roots = (const char **)ceed_parent->jit_source_roots;
1011   ceed_parent->num_jit_source_roots_readers++;
1012   CeedCall(CeedDestroy(&ceed_parent));
1013   return CEED_ERROR_SUCCESS;
1014 }
1015 
1016 /**
1017   @brief Restore list of additional JiT source roots from with @ref CeedGetJitSourceRoots()
1018 
1019   @param[in]  ceed             `Ceed` context
1020   @param[out] jit_source_roots Absolute paths to additional JiT source directories
1021 
1022   @return An error code: 0 - success, otherwise - failure
1023 
1024   @ref Backend
1025 **/
1026 int CeedRestoreJitSourceRoots(Ceed ceed, const char ***jit_source_roots) {
1027   Ceed ceed_parent;
1028 
1029   CeedCall(CeedGetParent(ceed, &ceed_parent));
1030   *jit_source_roots = NULL;
1031   ceed_parent->num_jit_source_roots_readers--;
1032   CeedCall(CeedDestroy(&ceed_parent));
1033   return CEED_ERROR_SUCCESS;
1034 }
1035 
1036 /**
1037   @brief Retrieve list of additional JiT defines from `Ceed` context.
1038 
1039   Note: The caller is responsible for restoring `jit_defines` with @ref CeedRestoreJitDefines().
1040 
1041   @param[in]  ceed            `Ceed` context
1042   @param[out] num_jit_defines Number of JiT defines
1043   @param[out] jit_defines     Strings such as `foo=bar`, used as `-Dfoo=bar` in JiT
1044 
1045   @return An error code: 0 - success, otherwise - failure
1046 
1047   @ref Backend
1048 **/
1049 int CeedGetJitDefines(Ceed ceed, CeedInt *num_jit_defines, const char ***jit_defines) {
1050   Ceed ceed_parent;
1051 
1052   CeedCall(CeedGetParent(ceed, &ceed_parent));
1053   *num_jit_defines = ceed_parent->num_jit_defines;
1054   *jit_defines     = (const char **)ceed_parent->jit_defines;
1055   ceed_parent->num_jit_defines_readers++;
1056   CeedCall(CeedDestroy(&ceed_parent));
1057   return CEED_ERROR_SUCCESS;
1058 }
1059 
1060 /**
1061   @brief Restore list of additional JiT defines from with @ref CeedGetJitDefines()
1062 
1063   @param[in]  ceed        `Ceed` context
1064   @param[out] jit_defines String such as `foo=bar`, used as `-Dfoo=bar` in JiT
1065 
1066   @return An error code: 0 - success, otherwise - failure
1067 
1068   @ref Backend
1069 **/
1070 int CeedRestoreJitDefines(Ceed ceed, const char ***jit_defines) {
1071   Ceed ceed_parent;
1072 
1073   CeedCall(CeedGetParent(ceed, &ceed_parent));
1074   *jit_defines = NULL;
1075   ceed_parent->num_jit_defines_readers--;
1076   CeedCall(CeedDestroy(&ceed_parent));
1077   return CEED_ERROR_SUCCESS;
1078 }
1079 
1080 /// @}
1081 
1082 /// ----------------------------------------------------------------------------
1083 /// Ceed Public API
1084 /// ----------------------------------------------------------------------------
1085 /// @addtogroup CeedUser
1086 /// @{
1087 
1088 /**
1089   @brief Get the list of available resource names for `Ceed` contexts
1090 
1091   Note: The caller is responsible for `free()`ing the resources and priorities arrays, but should not `free()` the contents of the resources array.
1092 
1093   @param[out] n          Number of available resources
1094   @param[out] resources  List of available resource names
1095   @param[out] priorities Resource name prioritization values, lower is better
1096 
1097   @return An error code: 0 - success, otherwise - failure
1098 
1099   @ref User
1100 **/
1101 // LCOV_EXCL_START
1102 int CeedRegistryGetList(size_t *n, char ***const resources, CeedInt **priorities) {
1103   *n         = 0;
1104   *resources = malloc(num_backends * sizeof(**resources));
1105   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "malloc() failure");
1106   if (priorities) {
1107     *priorities = malloc(num_backends * sizeof(**priorities));
1108     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "malloc() failure");
1109   }
1110   for (size_t i = 0; i < num_backends; i++) {
1111     // Only report compiled backends
1112     if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) {
1113       *resources[i] = backends[i].prefix;
1114       if (priorities) *priorities[i] = backends[i].priority;
1115       *n += 1;
1116     }
1117   }
1118   CeedCheck(*n, NULL, CEED_ERROR_MAJOR, "No backends installed");
1119   *resources = realloc(*resources, *n * sizeof(**resources));
1120   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "realloc() failure");
1121   if (priorities) {
1122     *priorities = realloc(*priorities, *n * sizeof(**priorities));
1123     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "realloc() failure");
1124   }
1125   return CEED_ERROR_SUCCESS;
1126 }
1127 // LCOV_EXCL_STOP
1128 
1129 /**
1130   @brief Initialize a `Ceed` context to use the specified resource.
1131 
1132   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`.
1133 
1134   @param[in]  resource Resource to use, e.g., "/cpu/self"
1135   @param[out] ceed     The library context
1136 
1137   @return An error code: 0 - success, otherwise - failure
1138 
1139   @ref User
1140 
1141   @sa CeedRegister() CeedDestroy()
1142 **/
1143 int CeedInit(const char *resource, Ceed *ceed) {
1144   size_t match_len = 0, match_index = UINT_MAX, match_priority = CEED_MAX_BACKEND_PRIORITY, priority;
1145 
1146   // Find matching backend
1147   CeedCheck(resource, NULL, CEED_ERROR_MAJOR, "No resource provided");
1148   CeedCall(CeedRegisterAll());
1149 
1150   // Check for help request
1151   const char *help_prefix = "help";
1152   size_t      match_help  = 0;
1153   while (match_help < 4 && resource[match_help] == help_prefix[match_help]) match_help++;
1154   if (match_help == 4) {
1155     fprintf(stderr, "libCEED version: %d.%d%d%s\n", CEED_VERSION_MAJOR, CEED_VERSION_MINOR, CEED_VERSION_PATCH,
1156             CEED_VERSION_RELEASE ? "" : "+development");
1157     fprintf(stderr, "Available backend resources:\n");
1158     for (size_t i = 0; i < num_backends; i++) {
1159       // Only report compiled backends
1160       if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) fprintf(stderr, "  %s\n", backends[i].prefix);
1161     }
1162     fflush(stderr);
1163     match_help = 5;  // Delineating character expected
1164   } else {
1165     match_help = 0;
1166   }
1167 
1168   // Find best match, computed as number of matching characters from requested resource stem
1169   size_t stem_length = 0;
1170   while (resource[stem_length + match_help] && resource[stem_length + match_help] != ':') stem_length++;
1171   for (size_t i = 0; i < num_backends; i++) {
1172     size_t      n      = 0;
1173     const char *prefix = backends[i].prefix;
1174     while (prefix[n] && prefix[n] == resource[n + match_help]) n++;
1175     priority = backends[i].priority;
1176     if (n > match_len || (n == match_len && match_priority > priority)) {
1177       match_len      = n;
1178       match_priority = priority;
1179       match_index    = i;
1180     }
1181   }
1182   // Using Levenshtein distance to find closest match
1183   if (match_len <= 1 || match_len != stem_length) {
1184     // LCOV_EXCL_START
1185     size_t lev_dis   = UINT_MAX;
1186     size_t lev_index = UINT_MAX, lev_priority = CEED_MAX_BACKEND_PRIORITY;
1187     for (size_t i = 0; i < num_backends; i++) {
1188       const char *prefix        = backends[i].prefix;
1189       size_t      prefix_length = strlen(backends[i].prefix);
1190       size_t      min_len       = (prefix_length < stem_length) ? prefix_length : stem_length;
1191       size_t      column[min_len + 1];
1192       for (size_t j = 0; j <= min_len; j++) column[j] = j;
1193       for (size_t j = 1; j <= min_len; j++) {
1194         column[0] = j;
1195         for (size_t k = 1, last_diag = j - 1; k <= min_len; k++) {
1196           size_t old_diag = column[k];
1197           size_t min_1    = (column[k] < column[k - 1]) ? column[k] + 1 : column[k - 1] + 1;
1198           size_t min_2    = last_diag + (resource[k - 1] == prefix[j - 1] ? 0 : 1);
1199           column[k]       = (min_1 < min_2) ? min_1 : min_2;
1200           last_diag       = old_diag;
1201         }
1202       }
1203       size_t n = column[min_len];
1204       priority = backends[i].priority;
1205       if (n < lev_dis || (n == lev_dis && lev_priority > priority)) {
1206         lev_dis      = n;
1207         lev_priority = priority;
1208         lev_index    = i;
1209       }
1210     }
1211     const char *prefix_lev = backends[lev_index].prefix;
1212     size_t      lev_length = 0;
1213     while (prefix_lev[lev_length] && prefix_lev[lev_length] != '\0') lev_length++;
1214     size_t m = (lev_length < stem_length) ? lev_length : stem_length;
1215     if (lev_dis + 1 >= m) return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s", resource);
1216     else return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s\nClosest match: %s", resource, backends[lev_index].prefix);
1217     // LCOV_EXCL_STOP
1218   }
1219 
1220   // Setup Ceed
1221   CeedCall(CeedCalloc(1, ceed));
1222   CeedCall(CeedCalloc(1, &(*ceed)->jit_source_roots));
1223   const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1224   if (!ceed_error_handler) ceed_error_handler = "abort";
1225   if (!strcmp(ceed_error_handler, "exit")) (*ceed)->Error = CeedErrorExit;
1226   else if (!strcmp(ceed_error_handler, "store")) (*ceed)->Error = CeedErrorStore;
1227   else (*ceed)->Error = CeedErrorAbort;
1228   memcpy((*ceed)->err_msg, "No error message stored", 24);
1229   (*ceed)->ref_count = 1;
1230   (*ceed)->data      = NULL;
1231 
1232   // Set lookup table
1233   FOffset f_offsets[] = {
1234       CEED_FTABLE_ENTRY(Ceed, Error),
1235       CEED_FTABLE_ENTRY(Ceed, SetStream),
1236       CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
1237       CEED_FTABLE_ENTRY(Ceed, Destroy),
1238       CEED_FTABLE_ENTRY(Ceed, VectorCreate),
1239       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
1240       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateAtPoints),
1241       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
1242       CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
1243       CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
1244       CEED_FTABLE_ENTRY(Ceed, BasisCreateHdiv),
1245       CEED_FTABLE_ENTRY(Ceed, BasisCreateHcurl),
1246       CEED_FTABLE_ENTRY(Ceed, TensorContractCreate),
1247       CEED_FTABLE_ENTRY(Ceed, QFunctionCreate),
1248       CEED_FTABLE_ENTRY(Ceed, QFunctionContextCreate),
1249       CEED_FTABLE_ENTRY(Ceed, OperatorCreate),
1250       CEED_FTABLE_ENTRY(Ceed, OperatorCreateAtPoints),
1251       CEED_FTABLE_ENTRY(Ceed, CompositeOperatorCreate),
1252       CEED_FTABLE_ENTRY(CeedVector, HasValidArray),
1253       CEED_FTABLE_ENTRY(CeedVector, HasBorrowedArrayOfType),
1254       CEED_FTABLE_ENTRY(CeedVector, CopyStrided),
1255       CEED_FTABLE_ENTRY(CeedVector, SetArray),
1256       CEED_FTABLE_ENTRY(CeedVector, TakeArray),
1257       CEED_FTABLE_ENTRY(CeedVector, SetValue),
1258       CEED_FTABLE_ENTRY(CeedVector, SetValueStrided),
1259       CEED_FTABLE_ENTRY(CeedVector, SyncArray),
1260       CEED_FTABLE_ENTRY(CeedVector, GetArray),
1261       CEED_FTABLE_ENTRY(CeedVector, GetArrayRead),
1262       CEED_FTABLE_ENTRY(CeedVector, GetArrayWrite),
1263       CEED_FTABLE_ENTRY(CeedVector, RestoreArray),
1264       CEED_FTABLE_ENTRY(CeedVector, RestoreArrayRead),
1265       CEED_FTABLE_ENTRY(CeedVector, Norm),
1266       CEED_FTABLE_ENTRY(CeedVector, Scale),
1267       CEED_FTABLE_ENTRY(CeedVector, AXPY),
1268       CEED_FTABLE_ENTRY(CeedVector, AXPBY),
1269       CEED_FTABLE_ENTRY(CeedVector, PointwiseMult),
1270       CEED_FTABLE_ENTRY(CeedVector, Reciprocal),
1271       CEED_FTABLE_ENTRY(CeedVector, Destroy),
1272       CEED_FTABLE_ENTRY(CeedElemRestriction, Apply),
1273       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnsigned),
1274       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnoriented),
1275       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyAtPointsInElement),
1276       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyBlock),
1277       CEED_FTABLE_ENTRY(CeedElemRestriction, GetOffsets),
1278       CEED_FTABLE_ENTRY(CeedElemRestriction, GetOrientations),
1279       CEED_FTABLE_ENTRY(CeedElemRestriction, GetCurlOrientations),
1280       CEED_FTABLE_ENTRY(CeedElemRestriction, GetAtPointsElementOffset),
1281       CEED_FTABLE_ENTRY(CeedElemRestriction, Destroy),
1282       CEED_FTABLE_ENTRY(CeedBasis, Apply),
1283       CEED_FTABLE_ENTRY(CeedBasis, ApplyAdd),
1284       CEED_FTABLE_ENTRY(CeedBasis, ApplyAtPoints),
1285       CEED_FTABLE_ENTRY(CeedBasis, ApplyAddAtPoints),
1286       CEED_FTABLE_ENTRY(CeedBasis, Destroy),
1287       CEED_FTABLE_ENTRY(CeedTensorContract, Apply),
1288       CEED_FTABLE_ENTRY(CeedTensorContract, Destroy),
1289       CEED_FTABLE_ENTRY(CeedQFunction, Apply),
1290       CEED_FTABLE_ENTRY(CeedQFunction, SetCUDAUserFunction),
1291       CEED_FTABLE_ENTRY(CeedQFunction, SetHIPUserFunction),
1292       CEED_FTABLE_ENTRY(CeedQFunction, Destroy),
1293       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasValidData),
1294       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasBorrowedDataOfType),
1295       CEED_FTABLE_ENTRY(CeedQFunctionContext, SetData),
1296       CEED_FTABLE_ENTRY(CeedQFunctionContext, TakeData),
1297       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetData),
1298       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetDataRead),
1299       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreData),
1300       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreDataRead),
1301       CEED_FTABLE_ENTRY(CeedQFunctionContext, DataDestroy),
1302       CEED_FTABLE_ENTRY(CeedQFunctionContext, Destroy),
1303       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunction),
1304       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunctionUpdate),
1305       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleDiagonal),
1306       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddDiagonal),
1307       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemblePointBlockDiagonal),
1308       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddPointBlockDiagonal),
1309       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSymbolic),
1310       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemble),
1311       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSingle),
1312       CEED_FTABLE_ENTRY(CeedOperator, CreateFDMElementInverse),
1313       CEED_FTABLE_ENTRY(CeedOperator, Apply),
1314       CEED_FTABLE_ENTRY(CeedOperator, ApplyComposite),
1315       CEED_FTABLE_ENTRY(CeedOperator, ApplyAdd),
1316       CEED_FTABLE_ENTRY(CeedOperator, ApplyAddComposite),
1317       CEED_FTABLE_ENTRY(CeedOperator, ApplyJacobian),
1318       CEED_FTABLE_ENTRY(CeedOperator, Destroy),
1319       {NULL, 0}  // End of lookup table - used in SetBackendFunction loop
1320   };
1321 
1322   CeedCall(CeedCalloc(sizeof(f_offsets), &(*ceed)->f_offsets));
1323   memcpy((*ceed)->f_offsets, f_offsets, sizeof(f_offsets));
1324 
1325   // Set fallback for advanced CeedOperator functions
1326   const char fallback_resource[] = "";
1327   CeedCall(CeedSetOperatorFallbackResource(*ceed, fallback_resource));
1328 
1329   // Record env variables CEED_DEBUG or DBG
1330   (*ceed)->is_debug = getenv("CEED_DEBUG") || getenv("DEBUG") || getenv("DBG");
1331 
1332   // Copy resource prefix, if backend setup successful
1333   CeedCall(CeedStringAllocCopy(backends[match_index].prefix, (char **)&(*ceed)->resource));
1334 
1335   // Set default JiT source root
1336   // Note: there will always be the default root for every Ceed but all additional paths are added to the top-most parent
1337   CeedCall(CeedAddJitSourceRoot(*ceed, (char *)CeedJitSourceRootDefault));
1338 
1339   // Backend specific setup
1340   CeedCall(backends[match_index].init(&resource[match_help], *ceed));
1341   return CEED_ERROR_SUCCESS;
1342 }
1343 
1344 /**
1345   @brief Set the GPU stream for a `Ceed` context
1346 
1347   @param[in,out] ceed   `Ceed` context to set the stream
1348   @param[in]     handle Handle to GPU stream
1349 
1350   @return An error code: 0 - success, otherwise - failure
1351 
1352   @ref User
1353 **/
1354 int CeedSetStream(Ceed ceed, void *handle) {
1355   CeedCheck(handle, ceed, CEED_ERROR_INCOMPATIBLE, "Stream handle must be non-NULL");
1356   if (ceed->SetStream) {
1357     CeedCall(ceed->SetStream(ceed, handle));
1358   } else {
1359     Ceed delegate;
1360     CeedCall(CeedGetDelegate(ceed, &delegate));
1361 
1362     if (delegate) CeedCall(CeedSetStream(delegate, handle));
1363     else return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Backend does not support setting stream");
1364     CeedCall(CeedDestroy(&delegate));
1365   }
1366   return CEED_ERROR_SUCCESS;
1367 }
1368 
1369 /**
1370   @brief Copy the pointer to a `Ceed` context.
1371 
1372   Both pointers should be destroyed with @ref CeedDestroy().
1373 
1374   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.
1375         This `Ceed` context will be destroyed if `*ceed_copy` is the only reference to this `Ceed` context.
1376 
1377   @param[in]     ceed      `Ceed` context to copy reference to
1378   @param[in,out] ceed_copy Variable to store copied reference
1379 
1380   @return An error code: 0 - success, otherwise - failure
1381 
1382   @ref User
1383 **/
1384 int CeedReferenceCopy(Ceed ceed, Ceed *ceed_copy) {
1385   CeedCall(CeedReference(ceed));
1386   CeedCall(CeedDestroy(ceed_copy));
1387   *ceed_copy = ceed;
1388   return CEED_ERROR_SUCCESS;
1389 }
1390 
1391 /**
1392   @brief Get the full resource name for a `Ceed` context
1393 
1394   @param[in]  ceed     `Ceed` context to get resource name of
1395   @param[out] resource Variable to store resource name
1396 
1397   @return An error code: 0 - success, otherwise - failure
1398 
1399   @ref User
1400 **/
1401 int CeedGetResource(Ceed ceed, const char **resource) {
1402   *resource = (const char *)ceed->resource;
1403   return CEED_ERROR_SUCCESS;
1404 }
1405 
1406 /**
1407   @brief Return `Ceed` context preferred memory type
1408 
1409   @param[in]  ceed     `Ceed` context to get preferred memory type of
1410   @param[out] mem_type Address to save preferred memory type to
1411 
1412   @return An error code: 0 - success, otherwise - failure
1413 
1414   @ref User
1415 **/
1416 int CeedGetPreferredMemType(Ceed ceed, CeedMemType *mem_type) {
1417   if (ceed->GetPreferredMemType) {
1418     CeedCall(ceed->GetPreferredMemType(mem_type));
1419   } else {
1420     Ceed delegate;
1421     CeedCall(CeedGetDelegate(ceed, &delegate));
1422 
1423     if (delegate) {
1424       CeedCall(CeedGetPreferredMemType(delegate, mem_type));
1425     } else {
1426       *mem_type = CEED_MEM_HOST;
1427     }
1428     CeedCall(CeedDestroy(&delegate));
1429   }
1430   return CEED_ERROR_SUCCESS;
1431 }
1432 
1433 /**
1434   @brief Get deterministic status of `Ceed` context
1435 
1436   @param[in]  ceed             `Ceed` context
1437   @param[out] is_deterministic Variable to store deterministic status
1438 
1439   @return An error code: 0 - success, otherwise - failure
1440 
1441   @ref User
1442 **/
1443 int CeedIsDeterministic(Ceed ceed, bool *is_deterministic) {
1444   *is_deterministic = ceed->is_deterministic;
1445   return CEED_ERROR_SUCCESS;
1446 }
1447 
1448 /**
1449   @brief Set additional JiT source root for `Ceed` context
1450 
1451   @param[in,out] ceed            `Ceed` context
1452   @param[in]     jit_source_root Absolute path to additional JiT source directory
1453 
1454   @return An error code: 0 - success, otherwise - failure
1455 
1456   @ref User
1457 **/
1458 int CeedAddJitSourceRoot(Ceed ceed, const char *jit_source_root) {
1459   Ceed ceed_parent;
1460 
1461   CeedCall(CeedGetParent(ceed, &ceed_parent));
1462   CeedCheck(!ceed_parent->num_jit_source_roots_readers, ceed, CEED_ERROR_ACCESS, "Cannot add JiT source root, read access has not been restored");
1463 
1464   CeedInt index       = ceed_parent->num_jit_source_roots;
1465   size_t  path_length = strlen(jit_source_root);
1466 
1467   if (ceed_parent->num_jit_source_roots == ceed_parent->max_jit_source_roots) {
1468     if (ceed_parent->max_jit_source_roots == 0) ceed_parent->max_jit_source_roots = 1;
1469     ceed_parent->max_jit_source_roots *= 2;
1470     CeedCall(CeedRealloc(ceed_parent->max_jit_source_roots, &ceed_parent->jit_source_roots));
1471   }
1472   CeedCall(CeedCalloc(path_length + 1, &ceed_parent->jit_source_roots[index]));
1473   memcpy(ceed_parent->jit_source_roots[index], jit_source_root, path_length);
1474   ceed_parent->num_jit_source_roots++;
1475   CeedCall(CeedDestroy(&ceed_parent));
1476   return CEED_ERROR_SUCCESS;
1477 }
1478 
1479 /**
1480   @brief Set additional JiT compiler define for `Ceed` context
1481 
1482   @param[in,out] ceed       `Ceed` context
1483   @param[in]     jit_define String such as `foo=bar`, used as `-Dfoo=bar` in JiT
1484 
1485   @return An error code: 0 - success, otherwise - failure
1486 
1487   @ref User
1488 **/
1489 int CeedAddJitDefine(Ceed ceed, const char *jit_define) {
1490   Ceed ceed_parent;
1491 
1492   CeedCall(CeedGetParent(ceed, &ceed_parent));
1493   CeedCheck(!ceed_parent->num_jit_defines_readers, ceed, CEED_ERROR_ACCESS, "Cannot add JiT define, read access has not been restored");
1494 
1495   CeedInt index         = ceed_parent->num_jit_defines;
1496   size_t  define_length = strlen(jit_define);
1497 
1498   if (ceed_parent->num_jit_defines == ceed_parent->max_jit_defines) {
1499     if (ceed_parent->max_jit_defines == 0) ceed_parent->max_jit_defines = 1;
1500     ceed_parent->max_jit_defines *= 2;
1501     CeedCall(CeedRealloc(ceed_parent->max_jit_defines, &ceed_parent->jit_defines));
1502   }
1503   CeedCall(CeedCalloc(define_length + 1, &ceed_parent->jit_defines[index]));
1504   memcpy(ceed_parent->jit_defines[index], jit_define, define_length);
1505   ceed_parent->num_jit_defines++;
1506   CeedCall(CeedDestroy(&ceed_parent));
1507   return CEED_ERROR_SUCCESS;
1508 }
1509 
1510 /**
1511   @brief View a `Ceed`
1512 
1513   @param[in] ceed   `Ceed` to view
1514   @param[in] stream Filestream to write to
1515 
1516   @return An error code: 0 - success, otherwise - failure
1517 
1518   @ref User
1519 **/
1520 int CeedView(Ceed ceed, FILE *stream) {
1521   CeedMemType mem_type;
1522 
1523   CeedCall(CeedGetPreferredMemType(ceed, &mem_type));
1524 
1525   fprintf(stream,
1526           "Ceed\n"
1527           "  Ceed Resource: %s\n"
1528           "  Preferred MemType: %s\n",
1529           ceed->resource, CeedMemTypes[mem_type]);
1530   return CEED_ERROR_SUCCESS;
1531 }
1532 
1533 /**
1534   @brief Destroy a `Ceed`
1535 
1536   @param[in,out] ceed Address of `Ceed` context to destroy
1537 
1538   @return An error code: 0 - success, otherwise - failure
1539 
1540   @ref User
1541 **/
1542 int CeedDestroy(Ceed *ceed) {
1543   if (!*ceed || --(*ceed)->ref_count > 0) {
1544     *ceed = NULL;
1545     return CEED_ERROR_SUCCESS;
1546   }
1547 
1548   CeedCheck(!(*ceed)->num_jit_source_roots_readers, *ceed, CEED_ERROR_ACCESS,
1549             "Cannot destroy ceed context, read access for JiT source roots has been granted");
1550   CeedCheck(!(*ceed)->num_jit_defines_readers, *ceed, CEED_ERROR_ACCESS, "Cannot add JiT source root, read access for JiT defines has been granted");
1551 
1552   if ((*ceed)->delegate) CeedCall(CeedDestroy(&(*ceed)->delegate));
1553 
1554   if ((*ceed)->obj_delegate_count > 0) {
1555     for (CeedInt i = 0; i < (*ceed)->obj_delegate_count; i++) {
1556       CeedCall(CeedDestroy(&((*ceed)->obj_delegates[i].delegate)));
1557       CeedCall(CeedFree(&(*ceed)->obj_delegates[i].obj_name));
1558     }
1559     CeedCall(CeedFree(&(*ceed)->obj_delegates));
1560   }
1561 
1562   if ((*ceed)->Destroy) CeedCall((*ceed)->Destroy(*ceed));
1563 
1564   for (CeedInt i = 0; i < (*ceed)->num_jit_source_roots; i++) {
1565     CeedCall(CeedFree(&(*ceed)->jit_source_roots[i]));
1566   }
1567   CeedCall(CeedFree(&(*ceed)->jit_source_roots));
1568 
1569   for (CeedInt i = 0; i < (*ceed)->num_jit_defines; i++) {
1570     CeedCall(CeedFree(&(*ceed)->jit_defines[i]));
1571   }
1572   CeedCall(CeedFree(&(*ceed)->jit_defines));
1573 
1574   CeedCall(CeedFree(&(*ceed)->f_offsets));
1575   CeedCall(CeedFree(&(*ceed)->resource));
1576   CeedCall(CeedDestroy(&(*ceed)->op_fallback_ceed));
1577   CeedCall(CeedFree(&(*ceed)->op_fallback_resource));
1578   CeedCall(CeedWorkVectorsDestroy(*ceed));
1579   CeedCall(CeedFree(ceed));
1580   return CEED_ERROR_SUCCESS;
1581 }
1582 
1583 // LCOV_EXCL_START
1584 const char *CeedErrorFormat(Ceed ceed, const char *format, va_list *args) {
1585   if (ceed->parent) return CeedErrorFormat(ceed->parent, format, args);
1586   if (ceed->op_fallback_parent) return CeedErrorFormat(ceed->op_fallback_parent, format, args);
1587   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1588   vsnprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, format, *args);  // NOLINT
1589   return ceed->err_msg;
1590 }
1591 // LCOV_EXCL_STOP
1592 
1593 /**
1594   @brief Error handling implementation; use @ref CeedError() instead.
1595 
1596   @return An error code: 0 - success, otherwise - failure
1597 
1598   @ref Developer
1599 **/
1600 int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func, int ecode, const char *format, ...) {
1601   va_list args;
1602   int     ret_val;
1603 
1604   va_start(args, format);
1605   if (ceed) {
1606     ret_val = ceed->Error(ceed, filename, lineno, func, ecode, format, &args);
1607   } else {
1608     // LCOV_EXCL_START
1609     const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1610     if (!ceed_error_handler) ceed_error_handler = "abort";
1611     if (!strcmp(ceed_error_handler, "return")) {
1612       ret_val = CeedErrorReturn(ceed, filename, lineno, func, ecode, format, &args);
1613     } else {
1614       // This function will not return
1615       ret_val = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, &args);
1616     }
1617   }
1618   va_end(args);
1619   return ret_val;
1620   // LCOV_EXCL_STOP
1621 }
1622 
1623 /**
1624   @brief Error handler that returns without printing anything.
1625 
1626   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1627 
1628   @return An error code: 0 - success, otherwise - failure
1629 
1630   @ref Developer
1631 **/
1632 // LCOV_EXCL_START
1633 int CeedErrorReturn(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1634   return err_code;
1635 }
1636 // LCOV_EXCL_STOP
1637 
1638 /**
1639   @brief Error handler that stores the error message for future use and returns the error.
1640 
1641   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1642 
1643   @return An error code: 0 - success, otherwise - failure
1644 
1645   @ref Developer
1646 **/
1647 // LCOV_EXCL_START
1648 int CeedErrorStore(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1649   if (ceed->parent) return CeedErrorStore(ceed->parent, filename, line_no, func, err_code, format, args);
1650   if (ceed->op_fallback_parent) return CeedErrorStore(ceed->op_fallback_parent, filename, line_no, func, err_code, format, args);
1651 
1652   // Build message
1653   int len = snprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, "%s:%d in %s(): ", filename, line_no, func);
1654   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1655   vsnprintf(ceed->err_msg + len, CEED_MAX_RESOURCE_LEN - len, format, *args);  // NOLINT
1656   return err_code;
1657 }
1658 // LCOV_EXCL_STOP
1659 
1660 /**
1661   @brief Error handler that prints to `stderr` and aborts
1662 
1663   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1664 
1665   @return An error code: 0 - success, otherwise - failure
1666 
1667   @ref Developer
1668 **/
1669 // LCOV_EXCL_START
1670 int CeedErrorAbort(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1671   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1672   vfprintf(stderr, format, *args);
1673   fprintf(stderr, "\n");
1674   abort();
1675   return err_code;
1676 }
1677 // LCOV_EXCL_STOP
1678 
1679 /**
1680   @brief Error handler that prints to `stderr` and exits.
1681 
1682   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1683 
1684   In contrast to @ref CeedErrorAbort(), this exits without a signal, so `atexit()` handlers (e.g., as used by gcov) are run.
1685 
1686   @return An error code: 0 - success, otherwise - failure
1687 
1688   @ref Developer
1689 **/
1690 int CeedErrorExit(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1691   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1692   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1693   vfprintf(stderr, format, *args);  // NOLINT
1694   fprintf(stderr, "\n");
1695   exit(err_code);
1696   return err_code;
1697 }
1698 
1699 /**
1700   @brief Set error handler
1701 
1702   A default error handler is set in @ref CeedInit().
1703   Use this function to change the error handler to @ref CeedErrorReturn(), @ref CeedErrorAbort(), or a user-defined error handler.
1704 
1705   @return An error code: 0 - success, otherwise - failure
1706 
1707   @ref Developer
1708 **/
1709 int CeedSetErrorHandler(Ceed ceed, CeedErrorHandler handler) {
1710   ceed->Error = handler;
1711   if (ceed->delegate) CeedSetErrorHandler(ceed->delegate, handler);
1712   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) CeedSetErrorHandler(ceed->obj_delegates[i].delegate, handler);
1713   return CEED_ERROR_SUCCESS;
1714 }
1715 
1716 /**
1717   @brief Get error message
1718 
1719   The error message is only stored when using the error handler @ref CeedErrorStore()
1720 
1721   @param[in]  ceed    `Ceed` context to retrieve error message
1722   @param[out] err_msg Char pointer to hold error message
1723 
1724   @return An error code: 0 - success, otherwise - failure
1725 
1726   @ref Developer
1727 **/
1728 int CeedGetErrorMessage(Ceed ceed, const char **err_msg) {
1729   if (ceed->parent) return CeedGetErrorMessage(ceed->parent, err_msg);
1730   if (ceed->op_fallback_parent) return CeedGetErrorMessage(ceed->op_fallback_parent, err_msg);
1731   *err_msg = ceed->err_msg;
1732   return CEED_ERROR_SUCCESS;
1733 }
1734 
1735 /**
1736   @brief Restore error message.
1737 
1738   The error message is only stored when using the error handler @ref CeedErrorStore().
1739 
1740   @param[in]  ceed    `Ceed` context to restore error message
1741   @param[out] err_msg Char pointer that holds error message
1742 
1743   @return An error code: 0 - success, otherwise - failure
1744 
1745   @ref Developer
1746 **/
1747 int CeedResetErrorMessage(Ceed ceed, const char **err_msg) {
1748   if (ceed->parent) return CeedResetErrorMessage(ceed->parent, err_msg);
1749   if (ceed->op_fallback_parent) return CeedResetErrorMessage(ceed->op_fallback_parent, err_msg);
1750   *err_msg = NULL;
1751   memcpy(ceed->err_msg, "No error message stored", 24);
1752   return CEED_ERROR_SUCCESS;
1753 }
1754 
1755 /**
1756   @brief Get libCEED library version information.
1757 
1758   libCEED version numbers have the form major.minor.patch.
1759   Non-release versions may contain unstable interfaces.
1760 
1761   @param[out] major   Major version of the library
1762   @param[out] minor   Minor version of the library
1763   @param[out] patch   Patch (subminor) version of the library
1764   @param[out] release True for releases; false for development branches
1765 
1766   The caller may pass `NULL` for any arguments that are not needed.
1767 
1768   @return An error code: 0 - success, otherwise - failure
1769 
1770   @ref Developer
1771 
1772   @sa CEED_VERSION_GE() CeedGetGitVersion() CeedGetBuildConfiguration()
1773 */
1774 int CeedGetVersion(int *major, int *minor, int *patch, bool *release) {
1775   if (major) *major = CEED_VERSION_MAJOR;
1776   if (minor) *minor = CEED_VERSION_MINOR;
1777   if (patch) *patch = CEED_VERSION_PATCH;
1778   if (release) *release = CEED_VERSION_RELEASE;
1779   return CEED_ERROR_SUCCESS;
1780 }
1781 
1782 /**
1783   @brief Get libCEED scalar type, such as F64 or F32
1784 
1785   @param[out] scalar_type Type of libCEED scalars
1786 
1787   @return An error code: 0 - success, otherwise - failure
1788 
1789   @ref Developer
1790 */
1791 int CeedGetScalarType(CeedScalarType *scalar_type) {
1792   *scalar_type = CEED_SCALAR_TYPE;
1793   return CEED_ERROR_SUCCESS;
1794 }
1795 
1796 /// @}
1797