xref: /libCEED/interface/ceed.c (revision 9bd0a4de615e3a1434ac7c89598f4ee8661d99f4)
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            Note: Backends should call `CeedRegister` instead.
114 
115   @param[in] prefix    Prefix of resources for this backend to respond to.
116                          For example, the reference backend responds to "/cpu/self".
117   @param[in] init      Initialization function called by CeedInit() when the backend is selected to drive the requested resource.
118   @param[in] priority  Integer priority.
119                          Lower values are preferred in case the resource requested by CeedInit() has non-unique best prefix match.
120 
121   @return An error code: 0 - success, otherwise - failure
122 
123   @ref Developer
124 **/
125 int CeedRegisterImpl(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
126   if (num_backends >= sizeof(backends) / sizeof(backends[0])) {
127     // LCOV_EXCL_START
128     return CeedError(NULL, CEED_ERROR_MAJOR, "Too many backends");
129     // LCOV_EXCL_STOP
130   }
131 
132   strncpy(backends[num_backends].prefix, prefix, CEED_MAX_RESOURCE_LEN);
133   backends[num_backends].prefix[CEED_MAX_RESOURCE_LEN - 1] = 0;
134   backends[num_backends].init                              = init;
135   backends[num_backends].priority                          = priority;
136   num_backends++;
137   return CEED_ERROR_SUCCESS;
138 }
139 
140 /// @}
141 
142 /// ----------------------------------------------------------------------------
143 /// Ceed Backend API
144 /// ----------------------------------------------------------------------------
145 /// @addtogroup CeedBackend
146 /// @{
147 
148 /**
149   @brief Return value of CEED_DEBUG environment variable
150 
151   @param[in] ceed Ceed context
152 
153   @return boolean value: true  - debugging mode enabled
154                          false - debugging mode disabled
155 
156   @ref Backend
157 **/
158 // LCOV_EXCL_START
159 bool CeedDebugFlag(const Ceed ceed) { return ceed->is_debug; }
160 // LCOV_EXCL_STOP
161 
162 /**
163   @brief Return value of CEED_DEBUG environment variable
164 
165   @return boolean value: true  - debugging mode enabled
166                          false - debugging mode disabled
167 
168   @ref Backend
169 **/
170 // LCOV_EXCL_START
171 bool CeedDebugFlagEnv(void) { return !!getenv("CEED_DEBUG") || !!getenv("DEBUG") || !!getenv("DBG"); }
172 // LCOV_EXCL_STOP
173 
174 /**
175   @brief Print debugging information in color
176 
177   @param color   Color to print
178   @param format  Printing format
179 
180   @ref Backend
181 **/
182 // LCOV_EXCL_START
183 void CeedDebugImpl256(const unsigned char color, const char *format, ...) {
184   va_list args;
185   va_start(args, format);
186   fflush(stdout);
187   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[38;5;%dm", color);
188   vfprintf(stdout, format, args);
189   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[m");
190   fprintf(stdout, "\n");
191   fflush(stdout);
192   va_end(args);
193 }
194 // LCOV_EXCL_STOP
195 
196 /**
197   @brief Allocate an array on the host; use CeedMalloc()
198 
199   Memory usage can be tracked by the library.
200   This ensures sufficient alignment for vectorization and should be used for large allocations.
201 
202   @param[in]  n    Number of units to allocate
203   @param[in]  unit Size of each unit
204   @param[out] p    Address of pointer to hold the result.
205 
206   @return An error code: 0 - success, otherwise - failure
207 
208   @sa CeedFree()
209 
210   @ref Backend
211 **/
212 int CeedMallocArray(size_t n, size_t unit, void *p) {
213   int ierr = posix_memalign((void **)p, CEED_ALIGN, n * unit);
214   if (ierr) {
215     // LCOV_EXCL_START
216     return CeedError(NULL, CEED_ERROR_MAJOR, "posix_memalign failed to allocate %zd members of size %zd\n", n, unit);
217     // LCOV_EXCL_STOP
218   }
219   return CEED_ERROR_SUCCESS;
220 }
221 
222 /**
223   @brief Allocate a cleared (zeroed) array on the host; use CeedCalloc()
224 
225   Memory usage can be tracked by the library.
226 
227   @param[in]  n    Number of units to allocate
228   @param[in]  unit Size of each unit
229   @param[out] p    Address of pointer to hold the result.
230 
231   @return An error code: 0 - success, otherwise - failure
232 
233   @sa CeedFree()
234 
235   @ref Backend
236 **/
237 int CeedCallocArray(size_t n, size_t unit, void *p) {
238   *(void **)p = calloc(n, unit);
239   if (n && unit && !*(void **)p) {
240     // LCOV_EXCL_START
241     return CeedError(NULL, CEED_ERROR_MAJOR, "calloc failed to allocate %zd members of size %zd\n", n, unit);
242     // LCOV_EXCL_STOP
243   }
244   return CEED_ERROR_SUCCESS;
245 }
246 
247 /**
248   @brief Reallocate an array on the host; use CeedRealloc()
249 
250   Memory usage can be tracked by the library.
251 
252   @param[in]  n    Number of units to allocate
253   @param[in]  unit Size of each unit
254   @param[out] p    Address of pointer to hold the result.
255 
256   @return An error code: 0 - success, otherwise - failure
257 
258   @sa CeedFree()
259 
260   @ref Backend
261 **/
262 int CeedReallocArray(size_t n, size_t unit, void *p) {
263   *(void **)p = realloc(*(void **)p, n * unit);
264   if (n && unit && !*(void **)p) {
265     // LCOV_EXCL_START
266     return CeedError(NULL, CEED_ERROR_MAJOR, "realloc failed to allocate %zd members of size %zd\n", n, unit);
267     // LCOV_EXCL_STOP
268   }
269   return CEED_ERROR_SUCCESS;
270 }
271 
272 /**
273   @brief Allocate a cleared string buffer on the host
274 
275   Memory usage can be tracked by the library.
276 
277   @param[in]  source Pointer to string to be copied
278   @param[out] copy   Pointer to variable to hold newly allocated string copy
279 
280   @return An error code: 0 - success, otherwise - failure
281 
282   @sa CeedFree()
283 
284   @ref Backend
285 **/
286 int CeedStringAllocCopy(const char *source, char **copy) {
287   size_t len = strlen(source);
288   CeedCall(CeedCalloc(len + 1, copy));
289   memcpy(*copy, source, len);
290   return CEED_ERROR_SUCCESS;
291 }
292 
293 /** Free memory allocated using CeedMalloc() or CeedCalloc()
294 
295   @param[in,out] p  address of pointer to memory.
296                       This argument is of type void* to avoid needing a cast, but is the address of the pointer (which is zeroed) rather than the
297 pointer.
298 **/
299 int CeedFree(void *p) {
300   free(*(void **)p);
301   *(void **)p = NULL;
302   return CEED_ERROR_SUCCESS;
303 }
304 
305 /**
306   @brief Register a Ceed backend
307 
308   @param[in] prefix   Prefix of resources for this backend to respond to.
309                         For example, the reference backend responds to "/cpu/self".
310   @param[in] init     Initialization function called by CeedInit() when the backend is selected to drive the requested resource.
311   @param[in] priority Integer priority.
312                         Lower values are preferred in case the resource requested by CeedInit() has non-unique best prefix match.
313 
314   @return An error code: 0 - success, otherwise - failure
315 
316   @ref Backend
317 **/
318 int CeedRegister(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
319   CeedDebugEnv("Backend Register: %s", prefix);
320   CeedRegisterImpl(prefix, init, priority);
321   return CEED_ERROR_SUCCESS;
322 }
323 
324 /**
325   @brief Return debugging status flag
326 
327   @param[in]  ceed     Ceed context to get debugging flag
328   @param[out] is_debug Variable to store debugging flag
329 
330   @return An error code: 0 - success, otherwise - failure
331 
332   @ref Backend
333 **/
334 int CeedIsDebug(Ceed ceed, bool *is_debug) {
335   *is_debug = ceed->is_debug;
336   return CEED_ERROR_SUCCESS;
337 }
338 
339 /**
340   @brief Retrieve a parent Ceed context
341 
342   @param[in]  ceed   Ceed context to retrieve parent of
343   @param[out] parent Address to save the parent to
344 
345   @return An error code: 0 - success, otherwise - failure
346 
347   @ref Backend
348 **/
349 int CeedGetParent(Ceed ceed, Ceed *parent) {
350   if (ceed->parent) {
351     CeedCall(CeedGetParent(ceed->parent, parent));
352     return CEED_ERROR_SUCCESS;
353   }
354   *parent = ceed;
355   return CEED_ERROR_SUCCESS;
356 }
357 
358 /**
359   @brief Retrieve a delegate Ceed context
360 
361   @param[in]  ceed     Ceed context to retrieve delegate of
362   @param[out] delegate Address to save the delegate to
363 
364   @return An error code: 0 - success, otherwise - failure
365 
366   @ref Backend
367 **/
368 int CeedGetDelegate(Ceed ceed, Ceed *delegate) {
369   *delegate = ceed->delegate;
370   return CEED_ERROR_SUCCESS;
371 }
372 
373 /**
374   @brief Set a delegate Ceed context
375 
376   This function allows a Ceed context to set a delegate Ceed context.
377     All backend implementations default to the delegate Ceed context, unless overridden.
378 
379   @param[in]  ceed     Ceed context to set delegate of
380   @param[out] delegate Address to set the delegate to
381 
382   @return An error code: 0 - success, otherwise - failure
383 
384   @ref Backend
385 **/
386 int CeedSetDelegate(Ceed ceed, Ceed delegate) {
387   ceed->delegate   = delegate;
388   delegate->parent = ceed;
389   return CEED_ERROR_SUCCESS;
390 }
391 
392 /**
393   @brief Retrieve a delegate Ceed context for a specific object type
394 
395   @param[in]  ceed     Ceed context to retrieve delegate of
396   @param[out] delegate Address to save the delegate to
397   @param[in]  obj_name Name of the object type to retrieve delegate for
398 
399   @return An error code: 0 - success, otherwise - failure
400 
401   @ref Backend
402 **/
403 int CeedGetObjectDelegate(Ceed ceed, Ceed *delegate, const char *obj_name) {
404   // Check for object delegate
405   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) {
406     if (!strcmp(obj_name, ceed->obj_delegates->obj_name)) {
407       *delegate = ceed->obj_delegates->delegate;
408       return CEED_ERROR_SUCCESS;
409     }
410   }
411 
412   // Use default delegate if no object delegate
413   CeedCall(CeedGetDelegate(ceed, delegate));
414   return CEED_ERROR_SUCCESS;
415 }
416 
417 /**
418   @brief Set a delegate Ceed context for a specific object type
419 
420   This function allows a Ceed context to set a delegate Ceed context for a given type of Ceed object.
421     All backend implementations default to the delegate Ceed context for this object.
422     For example, CeedSetObjectDelegate(ceed, refceed, "Basis") uses refceed implementations for all CeedBasis backend functions.
423 
424   @param[in,out] ceed     Ceed context to set delegate of
425   @param[out]    delegate Address to set the delegate to
426   @param[in]     obj_name Name of the object type to set delegate for
427 
428   @return An error code: 0 - success, otherwise - failure
429 
430   @ref Backend
431 **/
432 int CeedSetObjectDelegate(Ceed ceed, Ceed delegate, const char *obj_name) {
433   CeedInt count = ceed->obj_delegate_count;
434 
435   // Malloc or Realloc
436   if (count) {
437     CeedCall(CeedRealloc(count + 1, &ceed->obj_delegates));
438   } else {
439     CeedCall(CeedCalloc(1, &ceed->obj_delegates));
440   }
441   ceed->obj_delegate_count++;
442 
443   // Set object delegate
444   ceed->obj_delegates[count].delegate = delegate;
445   CeedCall(CeedStringAllocCopy(obj_name, &ceed->obj_delegates[count].obj_name));
446 
447   // Set delegate parent
448   delegate->parent = ceed;
449   return CEED_ERROR_SUCCESS;
450 }
451 
452 /**
453   @brief Get the fallback resource for CeedOperators
454 
455   @param[in]  ceed     Ceed context
456   @param[out] resource Variable to store fallback resource
457 
458   @return An error code: 0 - success, otherwise - failure
459 
460   @ref Backend
461 **/
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 
479 int CeedGetOperatorFallbackCeed(Ceed ceed, Ceed *fallback_ceed) {
480   if (ceed->has_valid_op_fallback_resource) {
481     CeedDebug256(ceed, 1, "---------- CeedOperator Fallback ----------\n");
482     CeedDebug(ceed, "Getting fallback from %s to %s\n", ceed->resource, ceed->op_fallback_resource);
483   }
484 
485   // Create fallback Ceed if uninitalized
486   if (!ceed->op_fallback_ceed && ceed->has_valid_op_fallback_resource) {
487     CeedDebug(ceed, "Creating fallback Ceed");
488 
489     Ceed        fallback_ceed;
490     const char *fallback_resource;
491 
492     CeedCall(CeedGetOperatorFallbackResource(ceed, &fallback_resource));
493     CeedCall(CeedInit(fallback_resource, &fallback_ceed));
494     fallback_ceed->op_fallback_parent = ceed;
495     fallback_ceed->Error              = ceed->Error;
496     ceed->op_fallback_ceed            = fallback_ceed;
497   }
498   *fallback_ceed = ceed->op_fallback_ceed;
499 
500   return CEED_ERROR_SUCCESS;
501 }
502 
503 /**
504   @brief Set the fallback resource for CeedOperators.
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 
516 int CeedSetOperatorFallbackResource(Ceed ceed, const char *resource) {
517   // Free old
518   CeedCall(CeedFree(&ceed->op_fallback_resource));
519 
520   // Set new
521   CeedCall(CeedStringAllocCopy(resource, (char **)&ceed->op_fallback_resource));
522 
523   // Check validity
524   ceed->has_valid_op_fallback_resource = ceed->op_fallback_resource && ceed->resource && strcmp(ceed->op_fallback_resource, ceed->resource);
525 
526   return CEED_ERROR_SUCCESS;
527 }
528 
529 /**
530   @brief Get the parent Ceed context associated with a fallback Ceed context for a CeedOperator
531 
532   @param[in]  ceed   Ceed context
533   @param[out] parent Variable to store parent Ceed context
534 
535   @return An error code: 0 - success, otherwise - failure
536 
537   @ref Backend
538 **/
539 
540 int CeedGetOperatorFallbackParentCeed(Ceed ceed, Ceed *parent) {
541   *parent = ceed->op_fallback_parent;
542   return CEED_ERROR_SUCCESS;
543 }
544 
545 /**
546   @brief Flag Ceed context as deterministic
547 
548   @param[in]  ceed             Ceed to flag as deterministic
549   @param[out] is_deterministic Deterministic status to set
550 
551   @return An error code: 0 - success, otherwise - failure
552 
553   @ref Backend
554 **/
555 
556 int CeedSetDeterministic(Ceed ceed, bool is_deterministic) {
557   ceed->is_deterministic = is_deterministic;
558   return CEED_ERROR_SUCCESS;
559 }
560 
561 /**
562   @brief Set a backend function
563 
564   This function is used for a backend to set the function associated with the Ceed objects.
565     For example, CeedSetBackendFunction(ceed, "Ceed", ceed, "VectorCreate", BackendVectorCreate) sets the backend implementation of 'CeedVectorCreate'
566 and CeedSetBackendFunction(ceed, "Basis", basis, "Apply", BackendBasisApply) sets the backend implementation of 'CeedBasisApply'. Note, the prefix
567 'Ceed' is not required for the object type ("Basis" vs "CeedBasis").
568 
569   @param[in]  ceed      Ceed context for error handling
570   @param[in]  type      Type of Ceed object to set function for
571   @param[out] object    Ceed object to set function for
572   @param[in]  func_name Name of function to set
573   @param[in]  f         Function to set
574 
575   @return An error code: 0 - success, otherwise - failure
576 
577   @ref Backend
578 **/
579 int CeedSetBackendFunction(Ceed ceed, const char *type, void *object, const char *func_name, int (*f)()) {
580   char lookup_name[CEED_MAX_RESOURCE_LEN + 1] = "";
581 
582   // Build lookup name
583   if (strcmp(type, "Ceed")) strncat(lookup_name, "Ceed", CEED_MAX_RESOURCE_LEN);
584   strncat(lookup_name, type, CEED_MAX_RESOURCE_LEN);
585   strncat(lookup_name, func_name, CEED_MAX_RESOURCE_LEN);
586 
587   // Find and use offset
588   for (CeedInt i = 0; ceed->f_offsets[i].func_name; i++) {
589     if (!strcmp(ceed->f_offsets[i].func_name, lookup_name)) {
590       size_t offset          = ceed->f_offsets[i].offset;
591       int (**fpointer)(void) = (int (**)(void))((char *)object + offset);  // *NOPAD*
592       *fpointer              = f;
593       return CEED_ERROR_SUCCESS;
594     }
595   }
596 
597   // LCOV_EXCL_START
598   return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Requested function '%s' was not found for CEED object '%s'", func_name, type);
599   // LCOV_EXCL_STOP
600 }
601 
602 /**
603   @brief Retrieve backend data for a Ceed context
604 
605   @param[in]  ceed Ceed context to retrieve data of
606   @param[out] data Address to save data to
607 
608   @return An error code: 0 - success, otherwise - failure
609 
610   @ref Backend
611 **/
612 int CeedGetData(Ceed ceed, void *data) {
613   *(void **)data = ceed->data;
614   return CEED_ERROR_SUCCESS;
615 }
616 
617 /**
618   @brief Set backend data for a Ceed context
619 
620   @param[in,out] ceed Ceed context to set data of
621   @param[in]     data Address of data to set
622 
623   @return An error code: 0 - success, otherwise - failure
624 
625   @ref Backend
626 **/
627 int CeedSetData(Ceed ceed, void *data) {
628   ceed->data = data;
629   return CEED_ERROR_SUCCESS;
630 }
631 
632 /**
633   @brief Increment the reference counter for a Ceed context
634 
635   @param[in,out] ceed Ceed context to increment the reference counter
636 
637   @return An error code: 0 - success, otherwise - failure
638 
639   @ref Backend
640 **/
641 int CeedReference(Ceed ceed) {
642   ceed->ref_count++;
643   return CEED_ERROR_SUCCESS;
644 }
645 
646 /// @}
647 
648 /// ----------------------------------------------------------------------------
649 /// Ceed Public API
650 /// ----------------------------------------------------------------------------
651 /// @addtogroup CeedUser
652 /// @{
653 
654 /**
655   @brief Get the list of available resource names for Ceed contexts
656            Note: The caller is responsible for `free()`ing the resources and priorities arrays, but should not `free()` the contents of the resources
657 array.
658 
659   @param[out] n          Number of available resources
660   @param[out] resources  List of available resource names
661   @param[out] priorities Resource name prioritization values, lower is better
662 
663   @return An error code: 0 - success, otherwise - failure
664 
665   @ref User
666 **/
667 // LCOV_EXCL_START
668 int CeedRegistryGetList(size_t *n, char ***const resources, CeedInt **priorities) {
669   *n         = 0;
670   *resources = malloc(num_backends * sizeof(**resources));
671   if (!resources) return CeedError(NULL, CEED_ERROR_MAJOR, "malloc() failure");
672   if (priorities) {
673     *priorities = malloc(num_backends * sizeof(**priorities));
674     if (!priorities) 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   }
689   *resources = realloc(*resources, *n * sizeof(**resources));
690   if (!resources) return CeedError(NULL, CEED_ERROR_MAJOR, "realloc() failure");
691   if (priorities) {
692     *priorities = realloc(*priorities, *n * sizeof(**priorities));
693     if (!priorities) return CeedError(NULL, CEED_ERROR_MAJOR, "realloc() failure");
694   }
695   return CEED_ERROR_SUCCESS;
696 }
697 // LCOV_EXCL_STOP
698 
699 /**
700   @brief Initialize a \ref Ceed context to use the specified resource.
701            Note: Prefixing the resource with "help:" (e.g. "help:/cpu/self") will result in CeedInt printing the current libCEED version number and a
702 list of current available backend resources to stderr.
703 
704   @param[in]  resource Resource to use, e.g., "/cpu/self"
705   @param[out] ceed     The library context
706   @sa CeedRegister() CeedDestroy()
707 
708   @return An error code: 0 - success, otherwise - failure
709 
710   @ref User
711 **/
712 int CeedInit(const char *resource, Ceed *ceed) {
713   size_t match_len = 0, match_index = UINT_MAX, match_priority = CEED_MAX_BACKEND_PRIORITY, priority;
714 
715   // Find matching backend
716   if (!resource) {
717     // LCOV_EXCL_START
718     return CeedError(NULL, CEED_ERROR_MAJOR, "No resource provided");
719     // LCOV_EXCL_STOP
720   }
721   CeedCall(CeedRegisterAll());
722 
723   // Check for help request
724   const char *help_prefix = "help";
725   size_t      match_help  = 0;
726   while (match_help < 4 && resource[match_help] == help_prefix[match_help]) match_help++;
727   if (match_help == 4) {
728     fprintf(stderr, "libCEED version: %d.%d%d%s\n", CEED_VERSION_MAJOR, CEED_VERSION_MINOR, CEED_VERSION_PATCH,
729             CEED_VERSION_RELEASE ? "" : "+development");
730     fprintf(stderr, "Available backend resources:\n");
731     for (size_t i = 0; i < num_backends; i++) {
732       // Only report compiled backends
733       if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) fprintf(stderr, "  %s\n", backends[i].prefix);
734     }
735     fflush(stderr);
736     match_help = 5;  // Delineating character expected
737   } else {
738     match_help = 0;
739   }
740 
741   // Find best match, computed as number of matching characters from requested resource stem
742   size_t stem_length = 0;
743   while (resource[stem_length + match_help] && resource[stem_length + match_help] != ':') stem_length++;
744   for (size_t i = 0; i < num_backends; i++) {
745     size_t      n      = 0;
746     const char *prefix = backends[i].prefix;
747     while (prefix[n] && prefix[n] == resource[n + match_help]) n++;
748     priority = backends[i].priority;
749     if (n > match_len || (n == match_len && match_priority > priority)) {
750       match_len      = n;
751       match_priority = priority;
752       match_index    = i;
753     }
754   }
755   // Using Levenshtein distance to find closest match
756   if (match_len <= 1 || match_len != stem_length) {
757     // LCOV_EXCL_START
758     size_t lev_dis   = UINT_MAX;
759     size_t lev_index = UINT_MAX, lev_priority = CEED_MAX_BACKEND_PRIORITY;
760     for (size_t i = 0; i < num_backends; i++) {
761       const char *prefix        = backends[i].prefix;
762       size_t      prefix_length = strlen(backends[i].prefix);
763       size_t      min_len       = (prefix_length < stem_length) ? prefix_length : stem_length;
764       size_t      column[min_len + 1];
765       for (size_t j = 0; j <= min_len; j++) column[j] = j;
766       for (size_t j = 1; j <= min_len; j++) {
767         column[0] = j;
768         for (size_t k = 1, last_diag = j - 1; k <= min_len; k++) {
769           size_t old_diag = column[k];
770           size_t min_1    = (column[k] < column[k - 1]) ? column[k] + 1 : column[k - 1] + 1;
771           size_t min_2    = last_diag + (resource[k - 1] == prefix[j - 1] ? 0 : 1);
772           column[k]       = (min_1 < min_2) ? min_1 : min_2;
773           last_diag       = old_diag;
774         }
775       }
776       size_t n = column[min_len];
777       priority = backends[i].priority;
778       if (n < lev_dis || (n == lev_dis && lev_priority > priority)) {
779         lev_dis      = n;
780         lev_priority = priority;
781         lev_index    = i;
782       }
783     }
784     const char *prefix_lev = backends[lev_index].prefix;
785     size_t      lev_length = 0;
786     while (prefix_lev[lev_length] && prefix_lev[lev_length] != '\0') lev_length++;
787     size_t m = (lev_length < stem_length) ? lev_length : stem_length;
788     if (lev_dis + 1 >= m) {
789       return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s", resource);
790     } else {
791       return CeedError(NULL, CEED_ERROR_MAJOR,
792                        "No suitable backend: %s\n"
793                        "Closest match: %s",
794                        resource, backends[lev_index].prefix);
795     }
796     // LCOV_EXCL_STOP
797   }
798 
799   // Setup Ceed
800   CeedCall(CeedCalloc(1, ceed));
801   CeedCall(CeedCalloc(1, &(*ceed)->jit_source_roots));
802   const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
803   if (!ceed_error_handler) ceed_error_handler = "abort";
804   if (!strcmp(ceed_error_handler, "exit")) (*ceed)->Error = CeedErrorExit;
805   else if (!strcmp(ceed_error_handler, "store")) (*ceed)->Error = CeedErrorStore;
806   else (*ceed)->Error = CeedErrorAbort;
807   memcpy((*ceed)->err_msg, "No error message stored", 24);
808   (*ceed)->ref_count = 1;
809   (*ceed)->data      = NULL;
810 
811   // Set lookup table
812   FOffset f_offsets[] = {
813       CEED_FTABLE_ENTRY(Ceed, Error),
814       CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
815       CEED_FTABLE_ENTRY(Ceed, Destroy),
816       CEED_FTABLE_ENTRY(Ceed, VectorCreate),
817       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
818       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateOriented),
819       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
820       CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
821       CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
822       CEED_FTABLE_ENTRY(Ceed, BasisCreateHdiv),
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            Both pointers should be destroyed with `CeedDestroy()`.
913 
914            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.
915              This Ceed context will be destroyed if `ceed_copy` is the only reference to this Ceed context.
916 
917   @param[in]     ceed      Ceed context to copy reference to
918   @param[in,out] ceed_copy Variable to store copied reference
919 
920   @return An error code: 0 - success, otherwise - failure
921 
922   @ref User
923 **/
924 int CeedReferenceCopy(Ceed ceed, Ceed *ceed_copy) {
925   CeedCall(CeedReference(ceed));
926   CeedCall(CeedDestroy(ceed_copy));
927   *ceed_copy = ceed;
928   return CEED_ERROR_SUCCESS;
929 }
930 
931 /**
932   @brief Get the full resource name for a Ceed context
933 
934   @param[in]  ceed     Ceed context to get resource name of
935   @param[out] resource Variable to store resource name
936 
937   @return An error code: 0 - success, otherwise - failure
938 
939   @ref User
940 **/
941 int CeedGetResource(Ceed ceed, const char **resource) {
942   *resource = (const char *)ceed->resource;
943   return CEED_ERROR_SUCCESS;
944 }
945 
946 /**
947   @brief Return Ceed context preferred memory type
948 
949   @param[in]  ceed     Ceed context to get preferred memory type of
950   @param[out] mem_type Address to save preferred memory type to
951 
952   @return An error code: 0 - success, otherwise - failure
953 
954   @ref User
955 **/
956 int CeedGetPreferredMemType(Ceed ceed, CeedMemType *mem_type) {
957   if (ceed->GetPreferredMemType) {
958     CeedCall(ceed->GetPreferredMemType(mem_type));
959   } else {
960     Ceed delegate;
961     CeedCall(CeedGetDelegate(ceed, &delegate));
962 
963     if (delegate) {
964       CeedCall(CeedGetPreferredMemType(delegate, mem_type));
965     } else {
966       *mem_type = CEED_MEM_HOST;
967     }
968   }
969   return CEED_ERROR_SUCCESS;
970 }
971 
972 /**
973   @brief Get deterministic status of Ceed
974 
975   @param[in]  ceed             Ceed
976   @param[out] is_deterministic Variable to store deterministic status
977 
978   @return An error code: 0 - success, otherwise - failure
979 
980   @ref User
981 **/
982 int CeedIsDeterministic(Ceed ceed, bool *is_deterministic) {
983   *is_deterministic = ceed->is_deterministic;
984   return CEED_ERROR_SUCCESS;
985 }
986 
987 /**
988   @brief Set additional JiT source root for Ceed
989 
990   @param[in,out] ceed            Ceed
991   @param[in]     jit_source_root Absolute path to additional JiT source directory
992 
993   @return An error code: 0 - success, otherwise - failure
994 
995   @ref User
996 **/
997 int CeedAddJitSourceRoot(Ceed ceed, const char *jit_source_root) {
998   Ceed ceed_parent;
999 
1000   CeedCall(CeedGetParent(ceed, &ceed_parent));
1001 
1002   CeedInt index       = ceed_parent->num_jit_source_roots;
1003   size_t  path_length = strlen(jit_source_root);
1004   CeedCall(CeedRealloc(index + 1, &ceed_parent->jit_source_roots));
1005   CeedCall(CeedCalloc(path_length + 1, &ceed_parent->jit_source_roots[index]));
1006   memcpy(ceed_parent->jit_source_roots[index], jit_source_root, path_length);
1007   ceed_parent->num_jit_source_roots++;
1008 
1009   return CEED_ERROR_SUCCESS;
1010 }
1011 
1012 /**
1013   @brief View a Ceed
1014 
1015   @param[in] ceed   Ceed to view
1016   @param[in] stream Filestream to write to
1017 
1018   @return An error code: 0 - success, otherwise - failure
1019 
1020   @ref User
1021 **/
1022 int CeedView(Ceed ceed, FILE *stream) {
1023   CeedMemType mem_type;
1024 
1025   CeedCall(CeedGetPreferredMemType(ceed, &mem_type));
1026 
1027   fprintf(stream,
1028           "Ceed\n"
1029           "  Ceed Resource: %s\n"
1030           "  Preferred MemType: %s\n",
1031           ceed->resource, CeedMemTypes[mem_type]);
1032   return CEED_ERROR_SUCCESS;
1033 }
1034 
1035 /**
1036   @brief Destroy a Ceed context
1037 
1038   @param[in,out] ceed Address of Ceed context to destroy
1039 
1040   @return An error code: 0 - success, otherwise - failure
1041 
1042   @ref User
1043 **/
1044 int CeedDestroy(Ceed *ceed) {
1045   if (!*ceed || --(*ceed)->ref_count > 0) {
1046     *ceed = NULL;
1047     return CEED_ERROR_SUCCESS;
1048   }
1049   if ((*ceed)->delegate) CeedCall(CeedDestroy(&(*ceed)->delegate));
1050 
1051   if ((*ceed)->obj_delegate_count > 0) {
1052     for (CeedInt i = 0; i < (*ceed)->obj_delegate_count; i++) {
1053       CeedCall(CeedDestroy(&((*ceed)->obj_delegates[i].delegate)));
1054       CeedCall(CeedFree(&(*ceed)->obj_delegates[i].obj_name));
1055     }
1056     CeedCall(CeedFree(&(*ceed)->obj_delegates));
1057   }
1058 
1059   if ((*ceed)->Destroy) CeedCall((*ceed)->Destroy(*ceed));
1060 
1061   for (CeedInt i = 0; i < (*ceed)->num_jit_source_roots; i++) {
1062     CeedCall(CeedFree(&(*ceed)->jit_source_roots[i]));
1063   }
1064   CeedCall(CeedFree(&(*ceed)->jit_source_roots));
1065 
1066   CeedCall(CeedFree(&(*ceed)->f_offsets));
1067   CeedCall(CeedFree(&(*ceed)->resource));
1068   CeedCall(CeedDestroy(&(*ceed)->op_fallback_ceed));
1069   CeedCall(CeedFree(&(*ceed)->op_fallback_resource));
1070   CeedCall(CeedFree(ceed));
1071   return CEED_ERROR_SUCCESS;
1072 }
1073 
1074 // LCOV_EXCL_START
1075 const char *CeedErrorFormat(Ceed ceed, const char *format, va_list *args) {
1076   if (ceed->parent) return CeedErrorFormat(ceed->parent, format, args);
1077   if (ceed->op_fallback_parent) return CeedErrorFormat(ceed->op_fallback_parent, format, args);
1078   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1079   vsnprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, format, *args);  // NOLINT
1080   return ceed->err_msg;
1081 }
1082 // LCOV_EXCL_STOP
1083 
1084 /**
1085   @brief Error handling implementation; use \ref CeedError instead.
1086 
1087   @ref Developer
1088 **/
1089 int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func, int ecode, const char *format, ...) {
1090   va_list args;
1091   int     ret_val;
1092   va_start(args, format);
1093   if (ceed) {
1094     ret_val = ceed->Error(ceed, filename, lineno, func, ecode, format, &args);
1095   } else {
1096     // LCOV_EXCL_START
1097     const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1098     if (!ceed_error_handler) ceed_error_handler = "abort";
1099     if (!strcmp(ceed_error_handler, "return")) ret_val = CeedErrorReturn(ceed, filename, lineno, func, ecode, format, &args);
1100     else
1101       // This function will not return
1102       ret_val = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, &args);
1103   }
1104   va_end(args);
1105   return ret_val;
1106   // LCOV_EXCL_STOP
1107 }
1108 
1109 /**
1110   @brief Error handler that returns without printing anything.
1111 
1112   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1113 
1114   @ref Developer
1115 **/
1116 // LCOV_EXCL_START
1117 int CeedErrorReturn(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1118   return err_code;
1119 }
1120 // LCOV_EXCL_STOP
1121 
1122 /**
1123   @brief Error handler that stores the error message for future use and returns 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, const char *func, int err_code, const char *format, va_list *args) {
1131   if (ceed->parent) return CeedErrorStore(ceed->parent, filename, line_no, func, err_code, format, args);
1132   if (ceed->op_fallback_parent) return CeedErrorStore(ceed->op_fallback_parent, filename, line_no, func, err_code, format, args);
1133 
1134   // Build message
1135   int len;
1136   len = snprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, "%s:%d in %s(): ", filename, line_no, func);
1137   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1138   vsnprintf(ceed->err_msg + len, CEED_MAX_RESOURCE_LEN - len, format, *args);  // NOLINT
1139   return err_code;
1140 }
1141 // LCOV_EXCL_STOP
1142 
1143 /**
1144   @brief Error handler that prints to stderr and aborts
1145 
1146   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1147 
1148   @ref Developer
1149 **/
1150 // LCOV_EXCL_START
1151 int CeedErrorAbort(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1152   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1153   vfprintf(stderr, format, *args);
1154   fprintf(stderr, "\n");
1155   abort();
1156   return err_code;
1157 }
1158 // LCOV_EXCL_STOP
1159 
1160 /**
1161   @brief Error handler that prints to stderr and exits
1162 
1163   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1164 
1165   In contrast to CeedErrorAbort(), this exits without a signal, so atexit() handlers (e.g., as used by gcov) are run.
1166 
1167   @ref Developer
1168 **/
1169 int CeedErrorExit(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1170   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1171   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1172   vfprintf(stderr, format, *args);  // NOLINT
1173   fprintf(stderr, "\n");
1174   exit(err_code);
1175   return err_code;
1176 }
1177 
1178 /**
1179   @brief Set error handler
1180 
1181   A default error handler is set in CeedInit().
1182   Use this function to change the error handler to CeedErrorReturn(), CeedErrorAbort(), or a user-defined error handler.
1183 
1184   @ref Developer
1185 **/
1186 int CeedSetErrorHandler(Ceed ceed, CeedErrorHandler handler) {
1187   ceed->Error = handler;
1188   if (ceed->delegate) CeedSetErrorHandler(ceed->delegate, handler);
1189   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) CeedSetErrorHandler(ceed->obj_delegates[i].delegate, handler);
1190   return CEED_ERROR_SUCCESS;
1191 }
1192 
1193 /**
1194   @brief Get error message
1195 
1196   The error message is only stored when using the error handler CeedErrorStore()
1197 
1198   @param[in]  ceed    Ceed context to retrieve error message
1199   @param[out] err_msg Char pointer to hold error message
1200 
1201   @ref Developer
1202 **/
1203 int CeedGetErrorMessage(Ceed ceed, const char **err_msg) {
1204   if (ceed->parent) return CeedGetErrorMessage(ceed->parent, err_msg);
1205   if (ceed->op_fallback_parent) return CeedGetErrorMessage(ceed->op_fallback_parent, err_msg);
1206   *err_msg = ceed->err_msg;
1207   return CEED_ERROR_SUCCESS;
1208 }
1209 
1210 /**
1211   @brief Restore error message
1212 
1213   The error message is only stored when using the error handler CeedErrorStore()
1214 
1215   @param[in]  ceed    Ceed context to restore error message
1216   @param[out] err_msg Char pointer that holds error message
1217 
1218   @ref Developer
1219 **/
1220 int CeedResetErrorMessage(Ceed ceed, const char **err_msg) {
1221   if (ceed->parent) return CeedResetErrorMessage(ceed->parent, err_msg);
1222   if (ceed->op_fallback_parent) return CeedResetErrorMessage(ceed->op_fallback_parent, err_msg);
1223   *err_msg = NULL;
1224   memcpy(ceed->err_msg, "No error message stored", 24);
1225   return CEED_ERROR_SUCCESS;
1226 }
1227 
1228 /**
1229   @brief Get libCEED library version info
1230 
1231   libCEED version numbers have the form major.minor.patch.
1232   Non-release versions may contain unstable interfaces.
1233 
1234   @param[out] major   Major version of the library
1235   @param[out] minor   Minor version of the library
1236   @param[out] patch   Patch (subminor) version of the library
1237   @param[out] release True for releases; false for development branches.
1238 
1239   The caller may pass NULL for any arguments that are not needed.
1240 
1241   @sa CEED_VERSION_GE()
1242 
1243   @ref Developer
1244 */
1245 int CeedGetVersion(int *major, int *minor, int *patch, bool *release) {
1246   if (major) *major = CEED_VERSION_MAJOR;
1247   if (minor) *minor = CEED_VERSION_MINOR;
1248   if (patch) *patch = CEED_VERSION_PATCH;
1249   if (release) *release = CEED_VERSION_RELEASE;
1250   return 0;
1251 }
1252 
1253 int CeedGetScalarType(CeedScalarType *scalar_type) {
1254   *scalar_type = CEED_SCALAR_TYPE;
1255   return 0;
1256 }
1257 
1258 /// @}
1259