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