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