xref: /libCEED/rust/libceed-sys/c-src/interface/ceed.c (revision e07206de23a3ad6f238e5da2d646d1a5a514f718)
1 // Copyright (c) 2017, Lawrence Livermore National Security, LLC. Produced at
2 // the Lawrence Livermore National Laboratory. LLNL-CODE-734707. All Rights
3 // reserved. See files LICENSE and NOTICE for details.
4 //
5 // This file is part of CEED, a collection of benchmarks, miniapps, software
6 // libraries and APIs for efficient high-order finite element and spectral
7 // element discretizations for exascale applications. For more information and
8 // source code availability see http://github.com/ceed.
9 //
10 // The CEED research is supported by the Exascale Computing Project 17-SC-20-SC,
11 // a collaborative effort of two U.S. Department of Energy organizations (Office
12 // of Science and the National Nuclear Security Administration) responsible for
13 // the planning and preparation of a capable exascale ecosystem, including
14 // software, applications, hardware, advanced system engineering and early
15 // testbed platforms, in support of the nation's exascale computing imperative.
16 
17 #define _POSIX_C_SOURCE 200112
18 #include <ceed-impl.h>
19 #include <ceed-backend.h>
20 #include <limits.h>
21 #include <stdarg.h>
22 #include <stddef.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 /// @cond DOXYGEN_SKIP
28 static CeedRequest ceed_request_immediate;
29 static CeedRequest ceed_request_ordered;
30 
31 static struct {
32   char prefix[CEED_MAX_RESOURCE_LEN];
33   int (*init)(const char *resource, Ceed f);
34   unsigned int priority;
35 } backends[32];
36 static size_t num_backends;
37 
38 #define CEED_FTABLE_ENTRY(class, method) \
39   {#class #method, offsetof(struct class ##_private, method)}
40 /// @endcond
41 
42 /// @file
43 /// Implementation of core components of Ceed library
44 ///
45 /// @addtogroup Ceed
46 /// @{
47 
48 /**
49   @brief Request immediate completion
50 
51   This predefined constant is passed as the \ref CeedRequest argument to
52   interfaces when the caller wishes for the operation to be performed
53   immediately.  The code
54 
55   @code
56     CeedOperatorApply(op, ..., CEED_REQUEST_IMMEDIATE);
57   @endcode
58 
59   is semantically equivalent to
60 
61   @code
62     CeedRequest request;
63     CeedOperatorApply(op, ..., &request);
64     CeedRequestWait(&request);
65   @endcode
66 
67   @sa CEED_REQUEST_ORDERED
68 **/
69 CeedRequest *const CEED_REQUEST_IMMEDIATE = &ceed_request_immediate;
70 
71 /**
72   @brief Request ordered completion
73 
74   This predefined constant is passed as the \ref CeedRequest argument to
75   interfaces when the caller wishes for the operation to be completed in the
76   order that it is submitted to the device.  It is typically used in a construct
77   such as
78 
79   @code
80     CeedRequest request;
81     CeedOperatorApply(op1, ..., CEED_REQUEST_ORDERED);
82     CeedOperatorApply(op2, ..., &request);
83     // other optional work
84     CeedWait(&request);
85   @endcode
86 
87   which allows the sequence to complete asynchronously but does not start
88   `op2` until `op1` has completed.
89 
90   @todo The current implementation is overly strict, offering equivalent
91   semantics to CEED_REQUEST_IMMEDIATE.
92 
93   @sa CEED_REQUEST_IMMEDIATE
94  */
95 CeedRequest *const CEED_REQUEST_ORDERED = &ceed_request_ordered;
96 
97 /**
98   @brief Error handling implementation; use \ref CeedError instead.
99 
100   @ref Developer
101 **/
102 int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func,
103                   int ecode, const char *format, ...) {
104   va_list args;
105   int retval;
106   va_start(args, format);
107   if (ceed) {
108     retval = ceed->Error(ceed, filename, lineno, func, ecode, format, args);
109   } else {
110     // This function doesn't actually return
111     retval = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, args);
112   }
113   va_end(args);
114   return retval;
115 }
116 
117 /**
118   @brief Error handler that returns without printing anything.
119 
120   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
121 
122   @ref Developer
123 **/
124 int CeedErrorReturn(Ceed ceed, const char *filename, int lineno,
125                     const char *func, int ecode, const char *format,
126                     va_list args) {
127   return ecode;
128 }
129 
130 /**
131   @brief Error handler that prints to stderr and aborts
132 
133   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
134 
135   @ref Developer
136 **/
137 int CeedErrorAbort(Ceed ceed, const char *filename, int lineno,
138                    const char *func, int ecode, const char *format,
139                    va_list args) {
140   fprintf(stderr, "%s:%d in %s(): ", filename, lineno, func);
141   vfprintf(stderr, format, args);
142   fprintf(stderr, "\n");
143   abort();
144   return ecode;
145 }
146 
147 /**
148   @brief Error handler that prints to stderr and exits
149 
150   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
151 
152   In contrast to CeedErrorAbort(), this exits without a signal, so atexit()
153   handlers (e.g., as used by gcov) are run.
154 
155   @ref Developer
156 **/
157 int CeedErrorExit(Ceed ceed, const char *filename, int lineno,
158                   const char *func, int ecode,
159                   const char *format, va_list args) {
160   fprintf(stderr, "%s:%d in %s(): ", filename, lineno, func);
161   vfprintf(stderr, format, args);
162   fprintf(stderr, "\n");
163   exit(ecode);
164   return ecode;
165 }
166 
167 /**
168   @brief Set error handler
169 
170   A default error handler is set in CeedInit().  Use this function to change
171   the error handler to CeedErrorReturn(), CeedErrorAbort(), or a user-defined
172   error handler.
173 
174   @ref Developer
175 **/
176 int CeedSetErrorHandler(Ceed ceed,
177                         int (eh)(Ceed, const char *, int, const char *,
178                                  int, const char *, va_list)) {
179   ceed->Error = eh;
180   return 0;
181 }
182 
183 /**
184   @brief Register a Ceed backend
185 
186   @param prefix   Prefix of resources for this backend to respond to.  For
187                     example, the reference backend responds to "/cpu/self".
188   @param init     Initialization function called by CeedInit() when the backend
189                     is selected to drive the requested resource.
190   @param priority Integer priority.  Lower values are preferred in case the
191                     resource requested by CeedInit() has non-unique best prefix
192                     match.
193 
194   @return An error code: 0 - success, otherwise - failure
195 
196   @ref Advanced
197 **/
198 int CeedRegister(const char *prefix,
199                  int (*init)(const char *, Ceed), unsigned int priority) {
200   if (num_backends >= sizeof(backends) / sizeof(backends[0]))
201     // LCOV_EXCL_START
202     return CeedError(NULL, 1, "Too many backends");
203   // LCOV_EXCL_STOP
204 
205   strncpy(backends[num_backends].prefix, prefix, CEED_MAX_RESOURCE_LEN);
206   backends[num_backends].prefix[CEED_MAX_RESOURCE_LEN-1] = 0;
207   backends[num_backends].init = init;
208   backends[num_backends].priority = priority;
209   num_backends++;
210   return 0;
211 }
212 
213 /**
214   @brief Allocate an array on the host; use CeedMalloc()
215 
216   Memory usage can be tracked by the library.  This ensures sufficient
217     alignment for vectorization and should be used for large allocations.
218 
219   @param n Number of units to allocate
220   @param unit Size of each unit
221   @param p Address of pointer to hold the result.
222 
223   @return An error code: 0 - success, otherwise - failure
224 
225   @sa CeedFree()
226 
227   @ref Advanced
228 **/
229 int CeedMallocArray(size_t n, size_t unit, void *p) {
230   int ierr = posix_memalign((void **)p, CEED_ALIGN, n*unit);
231   if (ierr)
232     // LCOV_EXCL_START
233     return CeedError(NULL, ierr, "posix_memalign failed to allocate %zd "
234                      "members of size %zd\n", n, unit);
235   // LCOV_EXCL_STOP
236 
237   return 0;
238 }
239 
240 /**
241   @brief Allocate a cleared (zeroed) array on the host; use CeedCalloc()
242 
243   Memory usage can be tracked by the library.
244 
245   @param n Number of units to allocate
246   @param unit Size of each unit
247   @param p Address of pointer to hold the result.
248 
249   @return An error code: 0 - success, otherwise - failure
250 
251   @sa CeedFree()
252 
253   @ref Advanced
254 **/
255 int CeedCallocArray(size_t n, size_t unit, void *p) {
256   *(void **)p = calloc(n, unit);
257   if (n && unit && !*(void **)p)
258     // LCOV_EXCL_START
259     return CeedError(NULL, 1, "calloc failed to allocate %zd members of size "
260                      "%zd\n", n, unit);
261   // LCOV_EXCL_STOP
262 
263   return 0;
264 }
265 
266 /**
267   @brief Reallocate an array on the host; use CeedRealloc()
268 
269   Memory usage can be tracked by the library.
270 
271   @param n Number of units to allocate
272   @param unit Size of each unit
273   @param p Address of pointer to hold the result.
274 
275   @return An error code: 0 - success, otherwise - failure
276 
277   @sa CeedFree()
278 
279   @ref Advanced
280 **/
281 int CeedReallocArray(size_t n, size_t unit, void *p) {
282   *(void **)p = realloc(*(void **)p, n*unit);
283   if (n && unit && !*(void **)p)
284     // LCOV_EXCL_START
285     return CeedError(NULL, 1, "realloc failed to allocate %zd members of size "
286                      "%zd\n", n, unit);
287   // LCOV_EXCL_STOP
288 
289   return 0;
290 }
291 
292 /// Free memory allocated using CeedMalloc() or CeedCalloc()
293 ///
294 /// @param p address of pointer to memory.  This argument is of type void* to
295 /// avoid needing a cast, but is the address of the pointer (which is zeroed)
296 /// rather than the pointer.
297 int CeedFree(void *p) {
298   free(*(void **)p);
299   *(void **)p = NULL;
300   return 0;
301 }
302 
303 /**
304   @brief Wait for a CeedRequest to complete.
305 
306   Calling CeedRequestWait on a NULL request is a no-op.
307 
308   @param req Address of CeedRequest to wait for; zeroed on completion.
309 
310   @return An error code: 0 - success, otherwise - failure
311 
312   @ref Advanced
313 **/
314 int CeedRequestWait(CeedRequest *req) {
315   if (!*req)
316     return 0;
317   return CeedError(NULL, 2, "CeedRequestWait not implemented");
318 }
319 
320 /**
321   @brief Initialize a \ref Ceed to use the specified resource.
322 
323   @param resource  Resource to use, e.g., "/cpu/self"
324   @param ceed The library context
325   @sa CeedRegister() CeedDestroy()
326 
327   @return An error code: 0 - success, otherwise - failure
328 
329   @ref Basic
330 **/
331 int CeedInit(const char *resource, Ceed *ceed) {
332   int ierr;
333   size_t matchlen = 0, matchidx = UINT_MAX, matchpriority = UINT_MAX, priority;
334 
335   // Find matching backend
336   if (!resource)
337     return CeedError(NULL, 1, "No resource provided");
338   for (size_t i=0; i<num_backends; i++) {
339     size_t n;
340     const char *prefix = backends[i].prefix;
341     for (n = 0; prefix[n] && prefix[n] == resource[n]; n++) {}
342     priority = backends[i].priority;
343     if (n > matchlen || (n == matchlen && matchpriority > priority)) {
344       matchlen = n;
345       matchpriority = priority;
346       matchidx = i;
347     }
348   }
349   if (!matchlen)
350     return CeedError(NULL, 1, "No suitable backend");
351 
352   // Setup Ceed
353   ierr = CeedCalloc(1,ceed); CeedChk(ierr);
354   const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
355   if (!ceed_error_handler)
356     ceed_error_handler = "abort";
357   if (!strcmp(ceed_error_handler, "exit"))
358     (*ceed)->Error = CeedErrorExit;
359   else
360     (*ceed)->Error = CeedErrorAbort;
361   (*ceed)->refcount = 1;
362   (*ceed)->data = NULL;
363 
364   // Set lookup table
365   foffset foffsets[] = {
366     CEED_FTABLE_ENTRY(Ceed, Error),
367     CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
368     CEED_FTABLE_ENTRY(Ceed, Destroy),
369     CEED_FTABLE_ENTRY(Ceed, VectorCreate),
370     CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
371     CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
372     CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
373     CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
374     CEED_FTABLE_ENTRY(Ceed, TensorContractCreate),
375     CEED_FTABLE_ENTRY(Ceed, QFunctionCreate),
376     CEED_FTABLE_ENTRY(Ceed, OperatorCreate),
377     CEED_FTABLE_ENTRY(Ceed, CompositeOperatorCreate),
378     CEED_FTABLE_ENTRY(CeedVector, SetArray),
379     CEED_FTABLE_ENTRY(CeedVector, SetValue),
380     CEED_FTABLE_ENTRY(CeedVector, GetArray),
381     CEED_FTABLE_ENTRY(CeedVector, GetArrayRead),
382     CEED_FTABLE_ENTRY(CeedVector, RestoreArray),
383     CEED_FTABLE_ENTRY(CeedVector, RestoreArrayRead),
384     CEED_FTABLE_ENTRY(CeedVector, Destroy),
385     CEED_FTABLE_ENTRY(CeedElemRestriction, Apply),
386     CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyBlock),
387     CEED_FTABLE_ENTRY(CeedElemRestriction, Destroy),
388     CEED_FTABLE_ENTRY(CeedBasis, Apply),
389     CEED_FTABLE_ENTRY(CeedBasis, Destroy),
390     CEED_FTABLE_ENTRY(CeedTensorContract, Apply),
391     CEED_FTABLE_ENTRY(CeedTensorContract, Destroy),
392     CEED_FTABLE_ENTRY(CeedQFunction, Apply),
393     CEED_FTABLE_ENTRY(CeedQFunction, Destroy),
394     CEED_FTABLE_ENTRY(CeedOperator, AssembleLinearQFunction),
395     CEED_FTABLE_ENTRY(CeedOperator, AssembleLinearDiagonal),
396     CEED_FTABLE_ENTRY(CeedOperator, Apply),
397     CEED_FTABLE_ENTRY(CeedOperator, ApplyJacobian),
398     CEED_FTABLE_ENTRY(CeedOperator, Destroy),
399     {NULL, 0} // End of lookup table - used in SetBackendFunction loop
400   };
401 
402   ierr = CeedCalloc(sizeof(foffsets), &(*ceed)->foffsets); CeedChk(ierr);
403   memcpy((*ceed)->foffsets, foffsets, sizeof(foffsets));
404 
405   // Backend specific setup
406   ierr = backends[matchidx].init(resource, *ceed); CeedChk(ierr);
407 
408   // Copy resource prefix, if backend setup sucessful
409   size_t len = strlen(backends[matchidx].prefix);
410   char *tmp;
411   ierr = CeedCalloc(len+1, &tmp); CeedChk(ierr);
412   memcpy(tmp, backends[matchidx].prefix, len+1);
413   (*ceed)->resource = tmp;
414 
415   return 0;
416 }
417 
418 /**
419   @brief Retrieve a parent CEED
420 
421   @param ceed           Ceed to retrieve parent of
422   @param[out] parent    Address to save the parent to
423 
424   @return An error code: 0 - success, otherwise - failure
425 
426   @ref Developer
427 **/
428 int CeedGetParent(Ceed ceed, Ceed *parent) {
429   int ierr;
430   if (ceed->parent) {
431     ierr = CeedGetParent(ceed->parent, parent); CeedChk(ierr);
432     return 0;
433   }
434   *parent = ceed;
435   return 0;
436 }
437 
438 /**
439   @brief Retrieve a delegate CEED
440 
441   @param ceed           Ceed to retrieve delegate of
442   @param[out] delegate  Address to save the delegate to
443 
444   @return An error code: 0 - success, otherwise - failure
445 
446   @ref Developer
447 **/
448 int CeedGetDelegate(Ceed ceed, Ceed *delegate) {
449   *delegate = ceed->delegate;
450   return 0;
451 }
452 
453 /**
454   @brief Set a delegate CEED
455 
456   This function allows a CEED to set a delegate CEED. All backend
457   implementations default to the delegate CEED, unless overridden.
458 
459   @param ceed           Ceed to set delegate of
460   @param[out] delegate  Address to set the delegate to
461 
462   @return An error code: 0 - success, otherwise - failure
463 
464   @ref Advanced
465 **/
466 int CeedSetDelegate(Ceed ceed, Ceed delegate) {
467   ceed->delegate = delegate;
468   delegate->parent = ceed;
469   return 0;
470 }
471 
472 /**
473   @brief Retrieve a delegate CEED for a specific object type
474 
475   @param ceed           Ceed to retrieve delegate of
476   @param[out] delegate  Address to save the delegate to
477   @param[in] objname    Name of the object type to retrieve delegate for
478 
479   @return An error code: 0 - success, otherwise - failure
480 
481   @ref Developer
482 **/
483 int CeedGetObjectDelegate(Ceed ceed, Ceed *delegate, const char *objname) {
484   CeedInt ierr;
485 
486   // Check for object delegate
487   for (CeedInt i=0; i<ceed->objdelegatecount; i++)
488     if (!strcmp(objname, ceed->objdelegates->objname)) {
489       *delegate = ceed->objdelegates->delegate;
490       return 0;
491     }
492 
493   // Use default delegate if no object delegate
494   ierr = CeedGetDelegate(ceed, delegate); CeedChk(ierr);
495 
496   return 0;
497 }
498 
499 /**
500   @brief Set a delegate CEED for a specific object type
501 
502   This function allows a CEED to set a delegate CEED for a given type of
503   CEED object. All backend implementations default to the delegate CEED for
504   this object. For example,
505     CeedSetObjectDelegate(ceed, refceed, "Basis")
506   uses refceed implementations for all CeedBasis backend functions.
507 
508   @param ceed           Ceed to set delegate of
509   @param[out] delegate  Address to set the delegate to
510   @param[in] objname    Name of the object type to set delegate for
511 
512   @return An error code: 0 - success, otherwise - failure
513 
514   @ref Advanced
515 **/
516 int CeedSetObjectDelegate(Ceed ceed, Ceed delegate, const char *objname) {
517   CeedInt ierr;
518   CeedInt count = ceed->objdelegatecount;
519 
520   // Malloc or Realloc
521   if (count) {
522     ierr = CeedRealloc(count+1, &ceed->objdelegates); CeedChk(ierr);
523   } else {
524     ierr = CeedCalloc(1, &ceed->objdelegates); CeedChk(ierr);
525   }
526   ceed->objdelegatecount++;
527 
528   // Set object delegate
529   ceed->objdelegates[count].delegate = delegate;
530   size_t slen = strlen(objname) + 1;
531   ierr = CeedMalloc(slen, &ceed->objdelegates[count].objname); CeedChk(ierr);
532   memcpy(ceed->objdelegates[count].objname, objname, slen);
533 
534   // Set delegate parent
535   delegate->parent = ceed;
536 
537   return 0;
538 }
539 
540 /**
541   @brief Return Ceed preferred memory type
542 
543   @param ceed           Ceed to get preferred memory type of
544   @param[out] type      Address to save preferred memory type to
545 
546   @return An error code: 0 - success, otherwise - failure
547 
548   @ref Basic
549 **/
550 int CeedGetPreferredMemType(Ceed ceed, CeedMemType *type) {
551   int ierr;
552 
553   if (ceed->GetPreferredMemType) {
554     ierr = ceed->GetPreferredMemType(type); CeedChk(ierr);
555   } else {
556     Ceed delegate;
557     ierr = CeedGetDelegate(ceed, &delegate); CeedChk(ierr);
558 
559     if (delegate) {
560       ierr = CeedGetPreferredMemType(delegate, type); CeedChk(ierr);
561     } else {
562       *type = CEED_MEM_HOST;
563     }
564   }
565 
566   return 0;
567 }
568 
569 /**
570   @brief Set a backend function
571 
572   This function is used for a backend to set the function associated with
573   the CEED objects. For example,
574     CeedSetBackendFunction(ceed, "Ceed", ceed, "VectorCreate", BackendVectorCreate)
575   sets the backend implementation of 'CeedVectorCreate' and
576     CeedSetBackendFunction(ceed, "Basis", basis, "Apply", BackendBasisApply)
577   sets the backend implementation of 'CeedBasisApply'. Note, the prefix 'Ceed'
578   is not required for the object type ("Basis" vs "CeedBasis").
579 
580   @param ceed           Ceed for error handling
581   @param type           Type of Ceed object to set function for
582   @param[out] object    Ceed object to set function for
583   @param fname          Name of function to set
584   @param f              Function to set
585 
586   @return An error code: 0 - success, otherwise - failure
587 
588   @ref Advanced
589 **/
590 int CeedSetBackendFunction(Ceed ceed,
591                            const char *type, void *object,
592                            const char *fname, int (*f)()) {
593   char lookupname[CEED_MAX_RESOURCE_LEN+1] = "";
594 
595   // Build lookup name
596   if (strcmp(type, "Ceed"))
597     strncat (lookupname, "Ceed", CEED_MAX_RESOURCE_LEN);
598   strncat(lookupname, type, CEED_MAX_RESOURCE_LEN);
599   strncat(lookupname, fname, CEED_MAX_RESOURCE_LEN);
600 
601   // Find and use offset
602   for (CeedInt i = 0; ceed->foffsets[i].fname; i++)
603     if (!strcmp(ceed->foffsets[i].fname, lookupname)) {
604       size_t offset = ceed->foffsets[i].offset;
605       int (**fpointer)(void) = (int (* *)(void))((char *)object + offset);
606       *fpointer = f;
607       return 0;
608     }
609 
610   // LCOV_EXCL_START
611   return CeedError(ceed, 1, "Requested function '%s' was not found for CEED "
612                    "object '%s'", fname, type);
613   // LCOV_EXCL_STOP
614 }
615 
616 /**
617   @brief Retrieve backend data for a CEED
618 
619   @param ceed           Ceed to retrieve data of
620   @param[out] data      Address to save data to
621 
622   @return An error code: 0 - success, otherwise - failure
623 
624   @ref Advanced
625 **/
626 int CeedGetData(Ceed ceed, void* *data) {
627   *data = ceed->data;
628   return 0;
629 }
630 
631 /**
632   @brief Set backend data for a CEED
633 
634   @param ceed           Ceed to set data of
635   @param data           Address of data to set
636 
637   @return An error code: 0 - success, otherwise - failure
638 
639   @ref Advanced
640 **/
641 int CeedSetData(Ceed ceed, void* *data) {
642   ceed->data = *data;
643   return 0;
644 }
645 
646 /**
647   @brief Get the full resource name for a CEED
648 
649   @param ceed            Ceed to get resource name of
650   @param[out] resource   Variable to store resource name
651 
652   @return An error code: 0 - success, otherwise - failure
653 
654   @ref Basic
655 **/
656 
657 int CeedGetResource(Ceed ceed, const char **resource) {
658   *resource = (const char *)ceed->resource;
659   return 0;
660 }
661 
662 /**
663   @brief Destroy a Ceed context
664 
665   @param ceed Address of Ceed context to destroy
666 
667   @return An error code: 0 - success, otherwise - failure
668 
669   @ref Basic
670 **/
671 int CeedDestroy(Ceed *ceed) {
672   int ierr;
673 
674   if (!*ceed || --(*ceed)->refcount > 0)
675     return 0;
676   if ((*ceed)->delegate) {
677     ierr = CeedDestroy(&(*ceed)->delegate); CeedChk(ierr);
678   }
679   if ((*ceed)->objdelegatecount > 0) {
680     for (int i=0; i<(*ceed)->objdelegatecount; i++) {
681       ierr = CeedDestroy(&((*ceed)->objdelegates[i].delegate)); CeedChk(ierr);
682       ierr = CeedFree(&(*ceed)->objdelegates[i].objname); CeedChk(ierr);
683     }
684     ierr = CeedFree(&(*ceed)->objdelegates); CeedChk(ierr);
685   }
686   if ((*ceed)->Destroy) {
687     ierr = (*ceed)->Destroy(*ceed); CeedChk(ierr);
688   }
689   ierr = CeedFree(&(*ceed)->foffsets); CeedChk(ierr);
690   ierr = CeedFree(&(*ceed)->resource); CeedChk(ierr);
691   ierr = CeedFree(ceed); CeedChk(ierr);
692   return 0;
693 }
694 
695 /// @}
696