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