xref: /libCEED/interface/ceed.c (revision 032e71eaa1e750e7a66514b4e3c9cd7b57405aab)
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   ierr = CeedCalloc(1, &(*ceed)->jit_source_roots); CeedChk(ierr);
811   (*ceed)->jit_source_roots[0] = CeedJitSourceRootDefault;
812   (*ceed)->num_jit_source_roots = 1;
813   const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
814   if (!ceed_error_handler)
815     ceed_error_handler = "abort";
816   if (!strcmp(ceed_error_handler, "exit"))
817     (*ceed)->Error = CeedErrorExit;
818   else if (!strcmp(ceed_error_handler, "store"))
819     (*ceed)->Error = CeedErrorStore;
820   else
821     (*ceed)->Error = CeedErrorAbort;
822   memcpy((*ceed)->err_msg, "No error message stored", 24);
823   (*ceed)->ref_count = 1;
824   (*ceed)->data = NULL;
825 
826   // Set lookup table
827   FOffset f_offsets[] = {
828     CEED_FTABLE_ENTRY(Ceed, Error),
829     CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
830     CEED_FTABLE_ENTRY(Ceed, Destroy),
831     CEED_FTABLE_ENTRY(Ceed, VectorCreate),
832     CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
833     CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateOriented),
834     CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
835     CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
836     CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
837     CEED_FTABLE_ENTRY(Ceed, BasisCreateHdiv),
838     CEED_FTABLE_ENTRY(Ceed, TensorContractCreate),
839     CEED_FTABLE_ENTRY(Ceed, QFunctionCreate),
840     CEED_FTABLE_ENTRY(Ceed, QFunctionContextCreate),
841     CEED_FTABLE_ENTRY(Ceed, OperatorCreate),
842     CEED_FTABLE_ENTRY(Ceed, CompositeOperatorCreate),
843     CEED_FTABLE_ENTRY(CeedVector, HasValidArray),
844     CEED_FTABLE_ENTRY(CeedVector, HasBorrowedArrayOfType),
845     CEED_FTABLE_ENTRY(CeedVector, SetArray),
846     CEED_FTABLE_ENTRY(CeedVector, TakeArray),
847     CEED_FTABLE_ENTRY(CeedVector, SetValue),
848     CEED_FTABLE_ENTRY(CeedVector, GetArray),
849     CEED_FTABLE_ENTRY(CeedVector, GetArrayRead),
850     CEED_FTABLE_ENTRY(CeedVector, GetArrayWrite),
851     CEED_FTABLE_ENTRY(CeedVector, RestoreArray),
852     CEED_FTABLE_ENTRY(CeedVector, RestoreArrayRead),
853     CEED_FTABLE_ENTRY(CeedVector, Norm),
854     CEED_FTABLE_ENTRY(CeedVector, Scale),
855     CEED_FTABLE_ENTRY(CeedVector, AXPY),
856     CEED_FTABLE_ENTRY(CeedVector, PointwiseMult),
857     CEED_FTABLE_ENTRY(CeedVector, Reciprocal),
858     CEED_FTABLE_ENTRY(CeedVector, Destroy),
859     CEED_FTABLE_ENTRY(CeedElemRestriction, Apply),
860     CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyBlock),
861     CEED_FTABLE_ENTRY(CeedElemRestriction, GetOffsets),
862     CEED_FTABLE_ENTRY(CeedElemRestriction, Destroy),
863     CEED_FTABLE_ENTRY(CeedBasis, Apply),
864     CEED_FTABLE_ENTRY(CeedBasis, Destroy),
865     CEED_FTABLE_ENTRY(CeedTensorContract, Apply),
866     CEED_FTABLE_ENTRY(CeedTensorContract, Destroy),
867     CEED_FTABLE_ENTRY(CeedQFunction, Apply),
868     CEED_FTABLE_ENTRY(CeedQFunction, SetCUDAUserFunction),
869     CEED_FTABLE_ENTRY(CeedQFunction, SetHIPUserFunction),
870     CEED_FTABLE_ENTRY(CeedQFunction, Destroy),
871     CEED_FTABLE_ENTRY(CeedQFunctionContext, HasValidData),
872     CEED_FTABLE_ENTRY(CeedQFunctionContext, HasBorrowedDataOfType),
873     CEED_FTABLE_ENTRY(CeedQFunctionContext, SetData),
874     CEED_FTABLE_ENTRY(CeedQFunctionContext, TakeData),
875     CEED_FTABLE_ENTRY(CeedQFunctionContext, GetData),
876     CEED_FTABLE_ENTRY(CeedQFunctionContext, GetDataRead),
877     CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreData),
878     CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreDataRead),
879     CEED_FTABLE_ENTRY(CeedQFunctionContext, Destroy),
880     CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunction),
881     CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunctionUpdate),
882     CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleDiagonal),
883     CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddDiagonal),
884     CEED_FTABLE_ENTRY(CeedOperator, LinearAssemblePointBlockDiagonal),
885     CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddPointBlockDiagonal),
886     CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSymbolic),
887     CEED_FTABLE_ENTRY(CeedOperator, LinearAssemble),
888     CEED_FTABLE_ENTRY(CeedOperator, CreateFDMElementInverse),
889     CEED_FTABLE_ENTRY(CeedOperator, Apply),
890     CEED_FTABLE_ENTRY(CeedOperator, ApplyComposite),
891     CEED_FTABLE_ENTRY(CeedOperator, ApplyAdd),
892     CEED_FTABLE_ENTRY(CeedOperator, ApplyAddComposite),
893     CEED_FTABLE_ENTRY(CeedOperator, ApplyJacobian),
894     CEED_FTABLE_ENTRY(CeedOperator, Destroy),
895     {NULL, 0} // End of lookup table - used in SetBackendFunction loop
896   };
897 
898   ierr = CeedCalloc(sizeof(f_offsets), &(*ceed)->f_offsets); CeedChk(ierr);
899   memcpy((*ceed)->f_offsets, f_offsets, sizeof(f_offsets));
900 
901   // Set fallback for advanced CeedOperator functions
902   const char fallbackresource[] = "";
903   ierr = CeedSetOperatorFallbackResource(*ceed, fallbackresource);
904   CeedChk(ierr);
905 
906   // Record env variables CEED_DEBUG or DBG
907   (*ceed)->is_debug = !!getenv("CEED_DEBUG") || !!getenv("DEBUG") ||
908                       !!getenv("DBG");
909 
910   // Backend specific setup
911   ierr = backends[match_index].init(&resource[match_help], *ceed); CeedChk(ierr);
912 
913   // Copy resource prefix, if backend setup successful
914   ierr = CeedStringAllocCopy(backends[match_index].prefix,
915                              (char **)&(*ceed)->resource);
916   CeedChk(ierr);
917   return CEED_ERROR_SUCCESS;
918 }
919 
920 /**
921   @brief Copy the pointer to a Ceed context. Both pointers should
922            be destroyed with `CeedDestroy()`;
923            Note: If `*ceed_copy` is non-NULL, then it is assumed that
924            `*ceed_copy` is a pointer to a Ceed context. This Ceed
925            context will be destroyed if `*ceed_copy` is the only
926            reference to this Ceed context.
927 
928   @param ceed            Ceed context to copy reference to
929   @param[out] ceed_copy  Variable to store copied reference
930 
931   @return An error code: 0 - success, otherwise - failure
932 
933   @ref User
934 **/
935 int CeedReferenceCopy(Ceed ceed, Ceed *ceed_copy) {
936   int ierr;
937 
938   ierr = CeedReference(ceed); CeedChk(ierr);
939   ierr = CeedDestroy(ceed_copy); CeedChk(ierr);
940   *ceed_copy = ceed;
941   return CEED_ERROR_SUCCESS;
942 }
943 
944 /**
945   @brief Get the full resource name for a Ceed context
946 
947   @param ceed           Ceed context to get resource name of
948   @param[out] resource  Variable to store resource name
949 
950   @return An error code: 0 - success, otherwise - failure
951 
952   @ref User
953 **/
954 int CeedGetResource(Ceed ceed, const char **resource) {
955   *resource = (const char *)ceed->resource;
956   return CEED_ERROR_SUCCESS;
957 }
958 
959 /**
960   @brief Return Ceed context preferred memory type
961 
962   @param ceed           Ceed context to get preferred memory type of
963   @param[out] mem_type  Address to save preferred memory type to
964 
965   @return An error code: 0 - success, otherwise - failure
966 
967   @ref User
968 **/
969 int CeedGetPreferredMemType(Ceed ceed, CeedMemType *mem_type) {
970   int ierr;
971 
972   if (ceed->GetPreferredMemType) {
973     ierr = ceed->GetPreferredMemType(mem_type); CeedChk(ierr);
974   } else {
975     Ceed delegate;
976     ierr = CeedGetDelegate(ceed, &delegate); CeedChk(ierr);
977 
978     if (delegate) {
979       ierr = CeedGetPreferredMemType(delegate, mem_type); CeedChk(ierr);
980     } else {
981       *mem_type = CEED_MEM_HOST;
982     }
983   }
984   return CEED_ERROR_SUCCESS;
985 }
986 
987 /**
988   @brief Get deterministic status of Ceed
989 
990   @param[in] ceed               Ceed
991   @param[out] is_deterministic  Variable to store deterministic status
992 
993   @return An error code: 0 - success, otherwise - failure
994 
995   @ref User
996 **/
997 int CeedIsDeterministic(Ceed ceed, bool *is_deterministic) {
998   *is_deterministic = ceed->is_deterministic;
999   return CEED_ERROR_SUCCESS;
1000 }
1001 
1002 /**
1003   @brief View a Ceed
1004 
1005   @param[in] ceed    Ceed to view
1006   @param[in] stream  Filestream to write to
1007 
1008   @return An error code: 0 - success, otherwise - failure
1009 
1010   @ref User
1011 **/
1012 int CeedView(Ceed ceed, FILE *stream) {
1013   int ierr;
1014   CeedMemType mem_type;
1015 
1016   ierr = CeedGetPreferredMemType(ceed, &mem_type); CeedChk(ierr);
1017 
1018   fprintf(stream, "Ceed\n"
1019           "  Ceed Resource: %s\n"
1020           "  Preferred MemType: %s\n",
1021           ceed->resource, CeedMemTypes[mem_type]);
1022   return CEED_ERROR_SUCCESS;
1023 }
1024 
1025 /**
1026   @brief Destroy a Ceed context
1027 
1028   @param ceed  Address of Ceed context to destroy
1029 
1030   @return An error code: 0 - success, otherwise - failure
1031 
1032   @ref User
1033 **/
1034 int CeedDestroy(Ceed *ceed) {
1035   int ierr;
1036   if (!*ceed || --(*ceed)->ref_count > 0) return CEED_ERROR_SUCCESS;
1037   if ((*ceed)->delegate) {
1038     ierr = CeedDestroy(&(*ceed)->delegate); CeedChk(ierr);
1039   }
1040 
1041   if ((*ceed)->obj_delegate_count > 0) {
1042     for (int i=0; i<(*ceed)->obj_delegate_count; i++) {
1043       ierr = CeedDestroy(&((*ceed)->obj_delegates[i].delegate)); CeedChk(ierr);
1044       ierr = CeedFree(&(*ceed)->obj_delegates[i].obj_name); CeedChk(ierr);
1045     }
1046     ierr = CeedFree(&(*ceed)->obj_delegates); CeedChk(ierr);
1047   }
1048 
1049   if ((*ceed)->Destroy) {
1050     ierr = (*ceed)->Destroy(*ceed); CeedChk(ierr);
1051   }
1052 
1053   for (int i=1; i < (*ceed)->num_jit_source_roots; i++) {
1054     ierr = CeedFree(&(*ceed)->jit_source_roots[i]); CeedChk(ierr);
1055   }
1056   ierr = CeedFree(&(*ceed)->jit_source_roots); CeedChk(ierr);
1057 
1058   ierr = CeedFree(&(*ceed)->f_offsets); CeedChk(ierr);
1059   ierr = CeedFree(&(*ceed)->resource); CeedChk(ierr);
1060   ierr = CeedDestroy(&(*ceed)->op_fallback_ceed); CeedChk(ierr);
1061   ierr = CeedFree(&(*ceed)->op_fallback_resource); CeedChk(ierr);
1062   ierr = CeedFree(ceed); CeedChk(ierr);
1063   return CEED_ERROR_SUCCESS;
1064 }
1065 
1066 // LCOV_EXCL_START
1067 const char *CeedErrorFormat(Ceed ceed, const char *format, va_list *args) {
1068   if (ceed->parent)
1069     return CeedErrorFormat(ceed->parent, format, args);
1070   if (ceed->op_fallback_parent)
1071     return CeedErrorFormat(ceed->op_fallback_parent, format, args);
1072   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1073   vsnprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, format, *args); // NOLINT
1074   return ceed->err_msg;
1075 }
1076 // LCOV_EXCL_STOP
1077 
1078 /**
1079   @brief Error handling implementation; use \ref CeedError instead.
1080 
1081   @ref Developer
1082 **/
1083 int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func,
1084                   int ecode, const char *format, ...) {
1085   va_list args;
1086   int ret_val;
1087   va_start(args, format);
1088   if (ceed) {
1089     ret_val = ceed->Error(ceed, filename, lineno, func, ecode, format, &args);
1090   } else {
1091     // LCOV_EXCL_START
1092     const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1093     if (!ceed_error_handler)
1094       ceed_error_handler = "abort";
1095     if (!strcmp(ceed_error_handler, "return"))
1096       ret_val = CeedErrorReturn(ceed, filename, lineno, func, ecode, format, &args);
1097     else
1098       // This function will not return
1099       ret_val = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, &args);
1100   }
1101   va_end(args);
1102   return ret_val;
1103   // LCOV_EXCL_STOP
1104 }
1105 
1106 /**
1107   @brief Error handler that returns without printing anything.
1108 
1109   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1110 
1111   @ref Developer
1112 **/
1113 // LCOV_EXCL_START
1114 int CeedErrorReturn(Ceed ceed, const char *filename, int line_no,
1115                     const char *func, int err_code, const char *format,
1116                     va_list *args) {
1117   return err_code;
1118 }
1119 // LCOV_EXCL_STOP
1120 
1121 /**
1122   @brief Error handler that stores the error message for future use and returns
1123            the error.
1124 
1125   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1126 
1127   @ref Developer
1128 **/
1129 // LCOV_EXCL_START
1130 int CeedErrorStore(Ceed ceed, const char *filename, int line_no,
1131                    const char *func, int err_code, const char *format,
1132                    va_list *args) {
1133   if (ceed->parent)
1134     return CeedErrorStore(ceed->parent, filename, line_no, func, err_code, format,
1135                           args);
1136   if (ceed->op_fallback_parent)
1137     return CeedErrorStore(ceed->op_fallback_parent, filename, line_no, func,
1138                           err_code, format, args);
1139 
1140   // Build message
1141   CeedInt len;
1142   len = snprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, "%s:%d in %s(): ",
1143                  filename, line_no, func);
1144   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1145   // *INDENT-OFF*
1146   vsnprintf(ceed->err_msg + len, CEED_MAX_RESOURCE_LEN - len, format, *args); // NOLINT
1147   // *INDENT-ON*
1148   return err_code;
1149 }
1150 // LCOV_EXCL_STOP
1151 
1152 /**
1153   @brief Error handler that prints to stderr and aborts
1154 
1155   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1156 
1157   @ref Developer
1158 **/
1159 // LCOV_EXCL_START
1160 int CeedErrorAbort(Ceed ceed, const char *filename, int line_no,
1161                    const char *func, int err_code, const char *format,
1162                    va_list *args) {
1163   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1164   vfprintf(stderr, format, *args);
1165   fprintf(stderr, "\n");
1166   abort();
1167   return err_code;
1168 }
1169 // LCOV_EXCL_STOP
1170 
1171 /**
1172   @brief Error handler that prints to stderr and exits
1173 
1174   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1175 
1176   In contrast to CeedErrorAbort(), this exits without a signal, so atexit()
1177   handlers (e.g., as used by gcov) are run.
1178 
1179   @ref Developer
1180 **/
1181 int CeedErrorExit(Ceed ceed, const char *filename, int line_no,
1182                   const char *func, int err_code, const char *format, va_list *args) {
1183   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1184   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1185   vfprintf(stderr, format, *args); // NOLINT
1186   fprintf(stderr, "\n");
1187   exit(err_code);
1188   return err_code;
1189 }
1190 
1191 /**
1192   @brief Set error handler
1193 
1194   A default error handler is set in CeedInit().  Use this function to change
1195   the error handler to CeedErrorReturn(), CeedErrorAbort(), or a user-defined
1196   error handler.
1197 
1198   @ref Developer
1199 **/
1200 int CeedSetErrorHandler(Ceed ceed, CeedErrorHandler handler) {
1201   ceed->Error = handler;
1202   if (ceed->delegate) CeedSetErrorHandler(ceed->delegate, handler);
1203   for (int i=0; i<ceed->obj_delegate_count; i++)
1204     CeedSetErrorHandler(ceed->obj_delegates[i].delegate, handler);
1205   return CEED_ERROR_SUCCESS;
1206 }
1207 
1208 /**
1209   @brief Get error message
1210 
1211   The error message is only stored when using the error handler
1212     CeedErrorStore()
1213 
1214   @param[in] ceed      Ceed contex to retrieve error message
1215   @param[out] err_msg  Char pointer to hold error message
1216 
1217   @ref Developer
1218 **/
1219 int CeedGetErrorMessage(Ceed ceed, const char **err_msg) {
1220   if (ceed->parent)
1221     return CeedGetErrorMessage(ceed->parent, err_msg);
1222   if (ceed->op_fallback_parent)
1223     return CeedGetErrorMessage(ceed->op_fallback_parent, err_msg);
1224   *err_msg = ceed->err_msg;
1225   return CEED_ERROR_SUCCESS;
1226 }
1227 
1228 /**
1229   @brief Restore error message
1230 
1231   The error message is only stored when using the error handler
1232     CeedErrorStore()
1233 
1234   @param[in] ceed      Ceed contex to restore error message
1235   @param[out] err_msg  Char pointer that holds error message
1236 
1237   @ref Developer
1238 **/
1239 int CeedResetErrorMessage(Ceed ceed, const char **err_msg) {
1240   if (ceed->parent)
1241     return CeedResetErrorMessage(ceed->parent, err_msg);
1242   if (ceed->op_fallback_parent)
1243     return CeedResetErrorMessage(ceed->op_fallback_parent, err_msg);
1244   *err_msg = NULL;
1245   memcpy(ceed->err_msg, "No error message stored", 24);
1246   return CEED_ERROR_SUCCESS;
1247 }
1248 
1249 /**
1250   @brief Get libCEED library version info
1251 
1252   libCEED version numbers have the form major.minor.patch. Non-release versions
1253   may contain unstable interfaces.
1254 
1255   @param[out] major    Major version of the library
1256   @param[out] minor    Minor version of the library
1257   @param[out] patch    Patch (subminor) version of the library
1258   @param[out] release  True for releases; false for development branches.
1259 
1260   The caller may pass NULL for any arguments that are not needed.
1261 
1262   @sa CEED_VERSION_GE()
1263 
1264   @ref Developer
1265 */
1266 int CeedGetVersion(int *major, int *minor, int *patch, bool *release) {
1267   if (major) *major = CEED_VERSION_MAJOR;
1268   if (minor) *minor = CEED_VERSION_MINOR;
1269   if (patch) *patch = CEED_VERSION_PATCH;
1270   if (release) *release = CEED_VERSION_RELEASE;
1271   return 0;
1272 }
1273 
1274 int CeedGetScalarType(CeedScalarType *scalar_type) {
1275   *scalar_type = CEED_SCALAR_TYPE;
1276   return 0;
1277 }
1278 
1279 
1280 /// @}
1281