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