xref: /libCEED/interface/ceed-operator.c (revision e7f679fc6de93686a880ec2b7b7a43611d8847a6)
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 #include <ceed-impl.h>
9 #include <ceed.h>
10 #include <ceed/backend.h>
11 #include <stdbool.h>
12 #include <stdio.h>
13 #include <string.h>
14 
15 /// @file
16 /// Implementation of CeedOperator interfaces
17 
18 /// ----------------------------------------------------------------------------
19 /// CeedOperator Library Internal Functions
20 /// ----------------------------------------------------------------------------
21 /// @addtogroup CeedOperatorDeveloper
22 /// @{
23 
24 /**
25   @brief Check if a CeedOperator Field matches the QFunction Field
26 
27   @param[in] ceed     Ceed object for error handling
28   @param[in] qf_field QFunction Field matching Operator Field
29   @param[in] r        Operator Field ElemRestriction
30   @param[in] b        Operator Field Basis
31 
32   @return An error code: 0 - success, otherwise - failure
33 
34   @ref Developer
35 **/
36 static int CeedOperatorCheckField(Ceed ceed, CeedQFunctionField qf_field, CeedElemRestriction r, CeedBasis b) {
37   CeedEvalMode eval_mode = qf_field->eval_mode;
38   CeedInt      dim = 1, num_comp = 1, q_comp = 1, restr_num_comp = 1, size = qf_field->size;
39 
40   // Restriction
41   CeedCheck((r == CEED_ELEMRESTRICTION_NONE) == (eval_mode == CEED_EVAL_WEIGHT), ceed, CEED_ERROR_INCOMPATIBLE,
42             "CEED_ELEMRESTRICTION_NONE and CEED_EVAL_WEIGHT must be used together.");
43   if (r != CEED_ELEMRESTRICTION_NONE) {
44     CeedCall(CeedElemRestrictionGetNumComponents(r, &restr_num_comp));
45   }
46   // Basis
47   CeedCheck((b == CEED_BASIS_COLLOCATED) == (eval_mode == CEED_EVAL_NONE), ceed, CEED_ERROR_INCOMPATIBLE,
48             "CEED_BASIS_COLLOCATED and CEED_EVAL_NONE must be used together.");
49   if (b != CEED_BASIS_COLLOCATED) {
50     CeedCall(CeedBasisGetDimension(b, &dim));
51     CeedCall(CeedBasisGetNumComponents(b, &num_comp));
52     CeedCall(CeedBasisGetNumQuadratureComponents(b, eval_mode, &q_comp));
53     CeedCheck(r == CEED_ELEMRESTRICTION_NONE || restr_num_comp == num_comp, ceed, CEED_ERROR_DIMENSION,
54               "Field '%s' of size %" CeedInt_FMT " and EvalMode %s: ElemRestriction has %" CeedInt_FMT " components, but Basis has %" CeedInt_FMT
55               " components",
56               qf_field->field_name, qf_field->size, CeedEvalModes[qf_field->eval_mode], restr_num_comp, num_comp);
57   }
58   // Field size
59   switch (eval_mode) {
60     case CEED_EVAL_NONE:
61       CeedCheck(size == restr_num_comp, ceed, CEED_ERROR_DIMENSION,
62                 "Field '%s' of size %" CeedInt_FMT " and EvalMode %s: ElemRestriction has %" CeedInt_FMT " components", qf_field->field_name,
63                 qf_field->size, CeedEvalModes[qf_field->eval_mode], restr_num_comp);
64       break;
65     case CEED_EVAL_INTERP:
66     case CEED_EVAL_GRAD:
67     case CEED_EVAL_DIV:
68     case CEED_EVAL_CURL:
69       CeedCheck(size == num_comp * q_comp, ceed, CEED_ERROR_DIMENSION,
70                 "Field '%s' of size %" CeedInt_FMT " and EvalMode %s: ElemRestriction/Basis has %" CeedInt_FMT " components", qf_field->field_name,
71                 qf_field->size, CeedEvalModes[qf_field->eval_mode], num_comp * q_comp);
72       break;
73     case CEED_EVAL_WEIGHT:
74       // No additional checks required
75       break;
76   }
77   return CEED_ERROR_SUCCESS;
78 }
79 
80 /**
81   @brief View a field of a CeedOperator
82 
83   @param[in] field        Operator field to view
84   @param[in] qf_field     QFunction field (carries field name)
85   @param[in] field_number Number of field being viewed
86   @param[in] sub          true indicates sub-operator, which increases indentation; false for top-level operator
87   @param[in] input        true for an input field; false for output field
88   @param[in] stream       Stream to view to, e.g., stdout
89 
90   @return An error code: 0 - success, otherwise - failure
91 
92   @ref Utility
93 **/
94 static int CeedOperatorFieldView(CeedOperatorField field, CeedQFunctionField qf_field, CeedInt field_number, bool sub, bool input, FILE *stream) {
95   const char *pre    = sub ? "  " : "";
96   const char *in_out = input ? "Input" : "Output";
97 
98   fprintf(stream,
99           "%s    %s field %" CeedInt_FMT
100           ":\n"
101           "%s      Name: \"%s\"\n",
102           pre, in_out, field_number, pre, qf_field->field_name);
103 
104   fprintf(stream, "%s      Size: %" CeedInt_FMT "\n", pre, qf_field->size);
105 
106   fprintf(stream, "%s      EvalMode: %s\n", pre, CeedEvalModes[qf_field->eval_mode]);
107 
108   if (field->basis == CEED_BASIS_COLLOCATED) fprintf(stream, "%s      Collocated basis\n", pre);
109 
110   if (field->vec == CEED_VECTOR_ACTIVE) fprintf(stream, "%s      Active vector\n", pre);
111   else if (field->vec == CEED_VECTOR_NONE) fprintf(stream, "%s      No vector\n", pre);
112 
113   return CEED_ERROR_SUCCESS;
114 }
115 
116 /**
117   @brief View a single CeedOperator
118 
119   @param[in] op     CeedOperator to view
120   @param[in] sub    Boolean flag for sub-operator
121   @param[in] stream Stream to write; typically stdout/stderr or a file
122 
123   @return Error code: 0 - success, otherwise - failure
124 
125   @ref Utility
126 **/
127 int CeedOperatorSingleView(CeedOperator op, bool sub, FILE *stream) {
128   const char *pre = sub ? "  " : "";
129 
130   CeedInt num_elem, num_qpts;
131   CeedCall(CeedOperatorGetNumElements(op, &num_elem));
132   CeedCall(CeedOperatorGetNumQuadraturePoints(op, &num_qpts));
133 
134   CeedInt total_fields = 0;
135   CeedCall(CeedOperatorGetNumArgs(op, &total_fields));
136   fprintf(stream, "%s  %" CeedInt_FMT " elements with %" CeedInt_FMT " quadrature points each\n", pre, num_elem, num_qpts);
137 
138   fprintf(stream, "%s  %" CeedInt_FMT " field%s\n", pre, total_fields, total_fields > 1 ? "s" : "");
139 
140   fprintf(stream, "%s  %" CeedInt_FMT " input field%s:\n", pre, op->qf->num_input_fields, op->qf->num_input_fields > 1 ? "s" : "");
141   for (CeedInt i = 0; i < op->qf->num_input_fields; i++) {
142     CeedCall(CeedOperatorFieldView(op->input_fields[i], op->qf->input_fields[i], i, sub, 1, stream));
143   }
144 
145   fprintf(stream, "%s  %" CeedInt_FMT " output field%s:\n", pre, op->qf->num_output_fields, op->qf->num_output_fields > 1 ? "s" : "");
146   for (CeedInt i = 0; i < op->qf->num_output_fields; i++) {
147     CeedCall(CeedOperatorFieldView(op->output_fields[i], op->qf->output_fields[i], i, sub, 0, stream));
148   }
149   return CEED_ERROR_SUCCESS;
150 }
151 
152 /**
153   @brief Find the active vector basis for a non-composite CeedOperator
154 
155   @param[in] op            CeedOperator to find active basis for
156   @param[out] active_basis Basis for active input vector or NULL for composite operator
157 
158   @return An error code: 0 - success, otherwise - failure
159 
160   @ref Developer
161 **/
162 int CeedOperatorGetActiveBasis(CeedOperator op, CeedBasis *active_basis) {
163   Ceed ceed;
164   CeedCall(CeedOperatorGetCeed(op, &ceed));
165 
166   *active_basis = NULL;
167   if (op->is_composite) return CEED_ERROR_SUCCESS;
168   for (CeedInt i = 0; i < op->qf->num_input_fields; i++) {
169     if (op->input_fields[i]->vec == CEED_VECTOR_ACTIVE) {
170       CeedCheck(!*active_basis || *active_basis == op->input_fields[i]->basis, ceed, CEED_ERROR_MINOR, "Multiple active CeedBases found");
171       *active_basis = op->input_fields[i]->basis;
172     }
173   }
174 
175   CeedCheck(*active_basis, ceed, CEED_ERROR_INCOMPLETE, "No active CeedBasis found");
176   return CEED_ERROR_SUCCESS;
177 }
178 
179 /**
180   @brief Find the active vector ElemRestriction for a non-composite CeedOperator
181 
182   @param[in] op           CeedOperator to find active basis for
183   @param[out] active_rstr ElemRestriction for active input vector or NULL for composite operator
184 
185   @return An error code: 0 - success, otherwise - failure
186 
187   @ref Utility
188 **/
189 int CeedOperatorGetActiveElemRestriction(CeedOperator op, CeedElemRestriction *active_rstr) {
190   Ceed ceed;
191   CeedCall(CeedOperatorGetCeed(op, &ceed));
192 
193   *active_rstr = NULL;
194   if (op->is_composite) return CEED_ERROR_SUCCESS;
195   for (CeedInt i = 0; i < op->qf->num_input_fields; i++) {
196     if (op->input_fields[i]->vec == CEED_VECTOR_ACTIVE) {
197       CeedCheck(!*active_rstr || *active_rstr == op->input_fields[i]->elem_rstr, ceed, CEED_ERROR_MINOR,
198                 "Multiple active CeedElemRestrictions found");
199       *active_rstr = op->input_fields[i]->elem_rstr;
200     }
201   }
202 
203   CeedCheck(*active_rstr, ceed, CEED_ERROR_INCOMPLETE, "No active CeedElemRestriction found");
204   return CEED_ERROR_SUCCESS;
205 }
206 
207 /**
208   @brief Set QFunctionContext field values of the specified type.
209 
210   For composite operators, the value is set in all sub-operator QFunctionContexts that have a matching `field_name`.
211   A non-zero error code is returned for single operators that do not have a matching field of the same type or composite operators that do not have
212 any field of a matching type.
213 
214   @param[in,out] op          CeedOperator
215   @param[in]     field_label Label of field to set
216   @param[in]     field_type  Type of field to set
217   @param[in]     values      Values to set
218 
219   @return An error code: 0 - success, otherwise - failure
220 
221   @ref User
222 **/
223 static int CeedOperatorContextSetGeneric(CeedOperator op, CeedContextFieldLabel field_label, CeedContextFieldType field_type, void *values) {
224   CeedCheck(field_label, op->ceed, CEED_ERROR_UNSUPPORTED, "Invalid field label");
225 
226   // Check if field_label and op correspond
227   if (field_label->from_op) {
228     CeedInt index = -1;
229 
230     for (CeedInt i = 0; i < op->num_context_labels; i++) {
231       if (op->context_labels[i] == field_label) index = i;
232     }
233     CeedCheck(index != -1, op->ceed, CEED_ERROR_UNSUPPORTED, "ContextFieldLabel does not correspond to the operator");
234   }
235 
236   bool is_composite = false;
237   CeedCall(CeedOperatorIsComposite(op, &is_composite));
238   if (is_composite) {
239     CeedInt       num_sub;
240     CeedOperator *sub_operators;
241 
242     CeedCall(CeedCompositeOperatorGetNumSub(op, &num_sub));
243     CeedCall(CeedCompositeOperatorGetSubList(op, &sub_operators));
244     CeedCheck(num_sub == field_label->num_sub_labels, op->ceed, CEED_ERROR_UNSUPPORTED,
245               "Composite operator modified after ContextFieldLabel created");
246 
247     for (CeedInt i = 0; i < num_sub; i++) {
248       // Try every sub-operator, ok if some sub-operators do not have field
249       if (field_label->sub_labels[i] && sub_operators[i]->qf->ctx) {
250         CeedCall(CeedQFunctionContextSetGeneric(sub_operators[i]->qf->ctx, field_label->sub_labels[i], field_type, values));
251       }
252     }
253   } else {
254     CeedCheck(op->qf->ctx, op->ceed, CEED_ERROR_UNSUPPORTED, "QFunction does not have context data");
255     CeedCall(CeedQFunctionContextSetGeneric(op->qf->ctx, field_label, field_type, values));
256   }
257 
258   return CEED_ERROR_SUCCESS;
259 }
260 
261 /**
262   @brief Get QFunctionContext field values of the specified type, read-only.
263 
264   For composite operators, the values retrieved are for the first sub-operator QFunctionContext that have a matching `field_name`.
265   A non-zero error code is returned for single operators that do not have a matching field of the same type or composite operators that do not have
266 any field of a matching type.
267 
268   @param[in,out] op          CeedOperator
269   @param[in]     field_label Label of field to set
270   @param[in]     field_type  Type of field to set
271   @param[out]    num_values  Number of values of type `field_type` in array `values`
272   @param[out]    values      Values in the label
273 
274   @return An error code: 0 - success, otherwise - failure
275 
276   @ref User
277 **/
278 static int CeedOperatorContextGetGenericRead(CeedOperator op, CeedContextFieldLabel field_label, CeedContextFieldType field_type, size_t *num_values,
279                                              void *values) {
280   CeedCheck(field_label, op->ceed, CEED_ERROR_UNSUPPORTED, "Invalid field label");
281 
282   *(void **)values = NULL;
283   *num_values      = 0;
284 
285   // Check if field_label and op correspond
286   if (field_label->from_op) {
287     CeedInt index = -1;
288 
289     for (CeedInt i = 0; i < op->num_context_labels; i++) {
290       if (op->context_labels[i] == field_label) index = i;
291     }
292     CeedCheck(index != -1, op->ceed, CEED_ERROR_UNSUPPORTED, "ContextFieldLabel does not correspond to the operator");
293   }
294 
295   bool is_composite = false;
296   CeedCall(CeedOperatorIsComposite(op, &is_composite));
297   if (is_composite) {
298     CeedInt       num_sub;
299     CeedOperator *sub_operators;
300 
301     CeedCall(CeedCompositeOperatorGetNumSub(op, &num_sub));
302     CeedCall(CeedCompositeOperatorGetSubList(op, &sub_operators));
303     CeedCheck(num_sub == field_label->num_sub_labels, op->ceed, CEED_ERROR_UNSUPPORTED,
304               "Composite operator modified after ContextFieldLabel created");
305 
306     for (CeedInt i = 0; i < num_sub; i++) {
307       // Try every sub-operator, ok if some sub-operators do not have field
308       if (field_label->sub_labels[i] && sub_operators[i]->qf->ctx) {
309         CeedCall(CeedQFunctionContextGetGenericRead(sub_operators[i]->qf->ctx, field_label->sub_labels[i], field_type, num_values, values));
310         return CEED_ERROR_SUCCESS;
311       }
312     }
313   } else {
314     CeedCheck(op->qf->ctx, op->ceed, CEED_ERROR_UNSUPPORTED, "QFunction does not have context data");
315     CeedCall(CeedQFunctionContextGetGenericRead(op->qf->ctx, field_label, field_type, num_values, values));
316   }
317 
318   return CEED_ERROR_SUCCESS;
319 }
320 
321 /**
322   @brief Restore QFunctionContext field values of the specified type, read-only.
323 
324   For composite operators, the values restored are for the first sub-operator QFunctionContext that have a matching `field_name`.
325   A non-zero error code is returned for single operators that do not have a matching field of the same type or composite operators that do not have
326 any field of a matching type.
327 
328   @param[in,out] op          CeedOperator
329   @param[in]     field_label Label of field to set
330   @param[in]     field_type  Type of field to set
331   @param[in]     values      Values array to restore
332 
333   @return An error code: 0 - success, otherwise - failure
334 
335   @ref User
336 **/
337 static int CeedOperatorContextRestoreGenericRead(CeedOperator op, CeedContextFieldLabel field_label, CeedContextFieldType field_type, void *values) {
338   CeedCheck(field_label, op->ceed, CEED_ERROR_UNSUPPORTED, "Invalid field label");
339 
340   // Check if field_label and op correspond
341   if (field_label->from_op) {
342     CeedInt index = -1;
343 
344     for (CeedInt i = 0; i < op->num_context_labels; i++) {
345       if (op->context_labels[i] == field_label) index = i;
346     }
347     CeedCheck(index != -1, op->ceed, CEED_ERROR_UNSUPPORTED, "ContextFieldLabel does not correspond to the operator");
348   }
349 
350   bool is_composite = false;
351   CeedCall(CeedOperatorIsComposite(op, &is_composite));
352   if (is_composite) {
353     CeedInt       num_sub;
354     CeedOperator *sub_operators;
355 
356     CeedCall(CeedCompositeOperatorGetNumSub(op, &num_sub));
357     CeedCall(CeedCompositeOperatorGetSubList(op, &sub_operators));
358     CeedCheck(num_sub == field_label->num_sub_labels, op->ceed, CEED_ERROR_UNSUPPORTED,
359               "Composite operator modified after ContextFieldLabel created");
360 
361     for (CeedInt i = 0; i < num_sub; i++) {
362       // Try every sub-operator, ok if some sub-operators do not have field
363       if (field_label->sub_labels[i] && sub_operators[i]->qf->ctx) {
364         CeedCall(CeedQFunctionContextRestoreGenericRead(sub_operators[i]->qf->ctx, field_label->sub_labels[i], field_type, values));
365         return CEED_ERROR_SUCCESS;
366       }
367     }
368   } else {
369     CeedCheck(op->qf->ctx, op->ceed, CEED_ERROR_UNSUPPORTED, "QFunction does not have context data");
370     CeedCall(CeedQFunctionContextRestoreGenericRead(op->qf->ctx, field_label, field_type, values));
371   }
372 
373   return CEED_ERROR_SUCCESS;
374 }
375 
376 /// @}
377 
378 /// ----------------------------------------------------------------------------
379 /// CeedOperator Backend API
380 /// ----------------------------------------------------------------------------
381 /// @addtogroup CeedOperatorBackend
382 /// @{
383 
384 /**
385   @brief Get the number of arguments associated with a CeedOperator
386 
387   @param[in]  op        CeedOperator
388   @param[out] num_args  Variable to store vector number of arguments
389 
390   @return An error code: 0 - success, otherwise - failure
391 
392   @ref Backend
393 **/
394 int CeedOperatorGetNumArgs(CeedOperator op, CeedInt *num_args) {
395   CeedCheck(!op->is_composite, op->ceed, CEED_ERROR_MINOR, "Not defined for composite operators");
396   *num_args = op->num_fields;
397   return CEED_ERROR_SUCCESS;
398 }
399 
400 /**
401   @brief Get the setup status of a CeedOperator
402 
403   @param[in]  op            CeedOperator
404   @param[out] is_setup_done Variable to store setup status
405 
406   @return An error code: 0 - success, otherwise - failure
407 
408   @ref Backend
409 **/
410 int CeedOperatorIsSetupDone(CeedOperator op, bool *is_setup_done) {
411   *is_setup_done = op->is_backend_setup;
412   return CEED_ERROR_SUCCESS;
413 }
414 
415 /**
416   @brief Get the QFunction associated with a CeedOperator
417 
418   @param[in]  op CeedOperator
419   @param[out] qf Variable to store QFunction
420 
421   @return An error code: 0 - success, otherwise - failure
422 
423   @ref Backend
424 **/
425 int CeedOperatorGetQFunction(CeedOperator op, CeedQFunction *qf) {
426   CeedCheck(!op->is_composite, op->ceed, CEED_ERROR_MINOR, "Not defined for composite operator");
427   *qf = op->qf;
428   return CEED_ERROR_SUCCESS;
429 }
430 
431 /**
432   @brief Get a boolean value indicating if the CeedOperator is composite
433 
434   @param[in]  op           CeedOperator
435   @param[out] is_composite Variable to store composite status
436 
437   @return An error code: 0 - success, otherwise - failure
438 
439   @ref Backend
440 **/
441 int CeedOperatorIsComposite(CeedOperator op, bool *is_composite) {
442   *is_composite = op->is_composite;
443   return CEED_ERROR_SUCCESS;
444 }
445 
446 /**
447   @brief Get the backend data of a CeedOperator
448 
449   @param[in]  op   CeedOperator
450   @param[out] data Variable to store data
451 
452   @return An error code: 0 - success, otherwise - failure
453 
454   @ref Backend
455 **/
456 int CeedOperatorGetData(CeedOperator op, void *data) {
457   *(void **)data = op->data;
458   return CEED_ERROR_SUCCESS;
459 }
460 
461 /**
462   @brief Set the backend data of a CeedOperator
463 
464   @param[in,out] op   CeedOperator
465   @param[in]     data Data to set
466 
467   @return An error code: 0 - success, otherwise - failure
468 
469   @ref Backend
470 **/
471 int CeedOperatorSetData(CeedOperator op, void *data) {
472   op->data = data;
473   return CEED_ERROR_SUCCESS;
474 }
475 
476 /**
477   @brief Increment the reference counter for a CeedOperator
478 
479   @param[in,out] op CeedOperator to increment the reference counter
480 
481   @return An error code: 0 - success, otherwise - failure
482 
483   @ref Backend
484 **/
485 int CeedOperatorReference(CeedOperator op) {
486   op->ref_count++;
487   return CEED_ERROR_SUCCESS;
488 }
489 
490 /**
491   @brief Set the setup flag of a CeedOperator to True
492 
493   @param[in,out] op CeedOperator
494 
495   @return An error code: 0 - success, otherwise - failure
496 
497   @ref Backend
498 **/
499 int CeedOperatorSetSetupDone(CeedOperator op) {
500   op->is_backend_setup = true;
501   return CEED_ERROR_SUCCESS;
502 }
503 
504 /// @}
505 
506 /// ----------------------------------------------------------------------------
507 /// CeedOperator Public API
508 /// ----------------------------------------------------------------------------
509 /// @addtogroup CeedOperatorUser
510 /// @{
511 
512 /**
513   @brief Create a CeedOperator and associate a CeedQFunction.
514 
515   A CeedBasis and CeedElemRestriction can be associated with CeedQFunction fields with \ref CeedOperatorSetField.
516 
517   @param[in]  ceed Ceed object where the CeedOperator will be created
518   @param[in]  qf   QFunction defining the action of the operator at quadrature points
519   @param[in]  dqf  QFunction defining the action of the Jacobian of @a qf (or @ref CEED_QFUNCTION_NONE)
520   @param[in]  dqfT QFunction defining the action of the transpose of the Jacobian of @a qf (or @ref CEED_QFUNCTION_NONE)
521   @param[out] op   Address of the variable where the newly created CeedOperator will be stored
522 
523   @return An error code: 0 - success, otherwise - failure
524 
525   @ref User
526  */
527 int CeedOperatorCreate(Ceed ceed, CeedQFunction qf, CeedQFunction dqf, CeedQFunction dqfT, CeedOperator *op) {
528   if (!ceed->OperatorCreate) {
529     Ceed delegate;
530 
531     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Operator"));
532     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not support OperatorCreate");
533     CeedCall(CeedOperatorCreate(delegate, qf, dqf, dqfT, op));
534     return CEED_ERROR_SUCCESS;
535   }
536 
537   CeedCheck(qf && qf != CEED_QFUNCTION_NONE, ceed, CEED_ERROR_MINOR, "Operator must have a valid QFunction.");
538 
539   CeedCall(CeedCalloc(1, op));
540   CeedCall(CeedReferenceCopy(ceed, &(*op)->ceed));
541   (*op)->ref_count   = 1;
542   (*op)->input_size  = -1;
543   (*op)->output_size = -1;
544   CeedCall(CeedQFunctionReferenceCopy(qf, &(*op)->qf));
545   if (dqf && dqf != CEED_QFUNCTION_NONE) CeedCall(CeedQFunctionReferenceCopy(dqf, &(*op)->dqf));
546   if (dqfT && dqfT != CEED_QFUNCTION_NONE) CeedCall(CeedQFunctionReferenceCopy(dqfT, &(*op)->dqfT));
547   CeedCall(CeedQFunctionAssemblyDataCreate(ceed, &(*op)->qf_assembled));
548   CeedCall(CeedCalloc(CEED_FIELD_MAX, &(*op)->input_fields));
549   CeedCall(CeedCalloc(CEED_FIELD_MAX, &(*op)->output_fields));
550   CeedCall(ceed->OperatorCreate(*op));
551   return CEED_ERROR_SUCCESS;
552 }
553 
554 /**
555   @brief Create an operator that composes the action of several operators
556 
557   @param[in]  ceed Ceed object where the CeedOperator will be created
558   @param[out] op   Address of the variable where the newly created Composite CeedOperator will be stored
559 
560   @return An error code: 0 - success, otherwise - failure
561 
562   @ref User
563  */
564 int CeedCompositeOperatorCreate(Ceed ceed, CeedOperator *op) {
565   if (!ceed->CompositeOperatorCreate) {
566     Ceed delegate;
567     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Operator"));
568 
569     if (delegate) {
570       CeedCall(CeedCompositeOperatorCreate(delegate, op));
571       return CEED_ERROR_SUCCESS;
572     }
573   }
574 
575   CeedCall(CeedCalloc(1, op));
576   CeedCall(CeedReferenceCopy(ceed, &(*op)->ceed));
577   (*op)->ref_count    = 1;
578   (*op)->is_composite = true;
579   CeedCall(CeedCalloc(CEED_COMPOSITE_MAX, &(*op)->sub_operators));
580   (*op)->input_size  = -1;
581   (*op)->output_size = -1;
582 
583   if (ceed->CompositeOperatorCreate) CeedCall(ceed->CompositeOperatorCreate(*op));
584   return CEED_ERROR_SUCCESS;
585 }
586 
587 /**
588   @brief Copy the pointer to a CeedOperator.
589 
590   Both pointers should be destroyed with `CeedOperatorDestroy()`.
591 
592   Note: If the value of `op_copy` passed to this function is non-NULL, then it is assumed that `op_copy` is a pointer to a CeedOperator.
593         This CeedOperator will be destroyed if `op_copy` is the only reference to this CeedOperator.
594 
595   @param[in]  op         CeedOperator to copy reference to
596   @param[in,out] op_copy Variable to store copied reference
597 
598   @return An error code: 0 - success, otherwise - failure
599 
600   @ref User
601 **/
602 int CeedOperatorReferenceCopy(CeedOperator op, CeedOperator *op_copy) {
603   CeedCall(CeedOperatorReference(op));
604   CeedCall(CeedOperatorDestroy(op_copy));
605   *op_copy = op;
606   return CEED_ERROR_SUCCESS;
607 }
608 
609 /**
610   @brief Provide a field to a CeedOperator for use by its CeedQFunction.
611 
612   This function is used to specify both active and passive fields to a CeedOperator.
613   For passive fields, a vector @arg v must be provided.
614   Passive fields can inputs or outputs (updated in-place when operator is applied).
615 
616   Active fields must be specified using this function, but their data (in a CeedVector) is passed in CeedOperatorApply().
617   There can be at most one active input CeedVector and at most one active output CeedVector passed to CeedOperatorApply().
618 
619   The number of quadrature points must agree across all points.
620   When using @ref CEED_BASIS_COLLOCATED, the number of quadrature points is determined by the element size of r.
621 
622   @param[in,out] op         CeedOperator on which to provide the field
623   @param[in]     field_name Name of the field (to be matched with the name used by CeedQFunction)
624   @param[in]     r          CeedElemRestriction
625   @param[in]     b          CeedBasis in which the field resides or @ref CEED_BASIS_COLLOCATED if collocated with quadrature points
626   @param[in]     v          CeedVector to be used by CeedOperator or @ref CEED_VECTOR_ACTIVE if field is active or @ref CEED_VECTOR_NONE
627                               if using @ref CEED_EVAL_WEIGHT in the QFunction
628 
629   @return An error code: 0 - success, otherwise - failure
630 
631   @ref User
632 **/
633 int CeedOperatorSetField(CeedOperator op, const char *field_name, CeedElemRestriction r, CeedBasis b, CeedVector v) {
634   CeedCheck(!op->is_composite, op->ceed, CEED_ERROR_INCOMPATIBLE, "Cannot add field to composite operator.");
635   CeedCheck(!op->is_immutable, op->ceed, CEED_ERROR_MAJOR, "Operator cannot be changed after set as immutable");
636   CeedCheck(r, op->ceed, CEED_ERROR_INCOMPATIBLE, "ElemRestriction r for field \"%s\" must be non-NULL.", field_name);
637   CeedCheck(b, op->ceed, CEED_ERROR_INCOMPATIBLE, "Basis b for field \"%s\" must be non-NULL.", field_name);
638   CeedCheck(v, op->ceed, CEED_ERROR_INCOMPATIBLE, "Vector v for field \"%s\" must be non-NULL.", field_name);
639 
640   CeedInt num_elem = 0;
641   CeedCall(CeedElemRestrictionGetNumElements(r, &num_elem));
642   CeedCheck(r == CEED_ELEMRESTRICTION_NONE || !op->has_restriction || op->num_elem == num_elem, op->ceed, CEED_ERROR_DIMENSION,
643             "ElemRestriction with %" CeedInt_FMT " elements incompatible with prior %" CeedInt_FMT " elements", num_elem, op->num_elem);
644 
645   CeedInt num_qpts = 0;
646   if (b == CEED_BASIS_COLLOCATED) CeedCall(CeedElemRestrictionGetElementSize(r, &num_qpts));
647   else CeedCall(CeedBasisGetNumQuadraturePoints(b, &num_qpts));
648   CeedCheck(op->num_qpts == 0 || op->num_qpts == num_qpts, op->ceed, CEED_ERROR_DIMENSION,
649             "%s must correspond to the same number of quadrature points as previously added Bases. Found %" CeedInt_FMT
650             " quadrature points but expected %" CeedInt_FMT " quadrature points.",
651             b == CEED_BASIS_COLLOCATED ? "ElemRestriction" : "Basis", num_qpts, op->num_qpts);
652   CeedQFunctionField qf_field;
653   CeedOperatorField *op_field;
654   bool               is_input = true;
655   for (CeedInt i = 0; i < op->qf->num_input_fields; i++) {
656     if (!strcmp(field_name, (*op->qf->input_fields[i]).field_name)) {
657       qf_field = op->qf->input_fields[i];
658       op_field = &op->input_fields[i];
659       goto found;
660     }
661   }
662   is_input = false;
663   for (CeedInt i = 0; i < op->qf->num_output_fields; i++) {
664     if (!strcmp(field_name, (*op->qf->output_fields[i]).field_name)) {
665       qf_field = op->qf->output_fields[i];
666       op_field = &op->output_fields[i];
667       goto found;
668     }
669   }
670   // LCOV_EXCL_START
671   return CeedError(op->ceed, CEED_ERROR_INCOMPLETE, "QFunction has no knowledge of field '%s'", field_name);
672   // LCOV_EXCL_STOP
673 found:
674   CeedCall(CeedOperatorCheckField(op->ceed, qf_field, r, b));
675   CeedCall(CeedCalloc(1, op_field));
676 
677   if (v == CEED_VECTOR_ACTIVE) {
678     CeedSize l_size;
679     CeedCall(CeedElemRestrictionGetLVectorSize(r, &l_size));
680     if (is_input) {
681       if (op->input_size == -1) op->input_size = l_size;
682       CeedCheck(l_size == op->input_size, op->ceed, CEED_ERROR_INCOMPATIBLE, "LVector size %td does not match previous size %td", l_size,
683                 op->input_size);
684     } else {
685       if (op->output_size == -1) op->output_size = l_size;
686       CeedCheck(l_size == op->output_size, op->ceed, CEED_ERROR_INCOMPATIBLE, "LVector size %td does not match previous size %td", l_size,
687                 op->output_size);
688     }
689   }
690 
691   CeedCall(CeedVectorReferenceCopy(v, &(*op_field)->vec));
692   CeedCall(CeedElemRestrictionReferenceCopy(r, &(*op_field)->elem_rstr));
693   if (r != CEED_ELEMRESTRICTION_NONE) {
694     op->num_elem        = num_elem;
695     op->has_restriction = true;  // Restriction set, but num_elem may be 0
696   }
697   CeedCall(CeedBasisReferenceCopy(b, &(*op_field)->basis));
698   if (op->num_qpts == 0) CeedCall(CeedOperatorSetNumQuadraturePoints(op, num_qpts));
699 
700   op->num_fields += 1;
701   CeedCall(CeedStringAllocCopy(field_name, (char **)&(*op_field)->field_name));
702   return CEED_ERROR_SUCCESS;
703 }
704 
705 /**
706   @brief Get the CeedOperatorFields of a CeedOperator
707 
708   Note: Calling this function asserts that setup is complete and sets the CeedOperator as immutable.
709 
710   @param[in]  op                CeedOperator
711   @param[out] num_input_fields  Variable to store number of input fields
712   @param[out] input_fields      Variable to store input_fields
713   @param[out] num_output_fields Variable to store number of output fields
714   @param[out] output_fields     Variable to store output_fields
715 
716   @return An error code: 0 - success, otherwise - failure
717 
718   @ref Advanced
719 **/
720 int CeedOperatorGetFields(CeedOperator op, CeedInt *num_input_fields, CeedOperatorField **input_fields, CeedInt *num_output_fields,
721                           CeedOperatorField **output_fields) {
722   CeedCheck(!op->is_composite, op->ceed, CEED_ERROR_MINOR, "Not defined for composite operator");
723   CeedCall(CeedOperatorCheckReady(op));
724 
725   if (num_input_fields) *num_input_fields = op->qf->num_input_fields;
726   if (input_fields) *input_fields = op->input_fields;
727   if (num_output_fields) *num_output_fields = op->qf->num_output_fields;
728   if (output_fields) *output_fields = op->output_fields;
729   return CEED_ERROR_SUCCESS;
730 }
731 
732 /**
733   @brief Get a CeedOperatorField of an CeedOperator from its name
734 
735   Note: Calling this function asserts that setup is complete and sets the CeedOperator as immutable.
736 
737   @param[in]  op         CeedOperator
738   @param[in]  field_name Name of desired CeedOperatorField
739   @param[out] op_field   CeedOperatorField corresponding to the name
740 
741   @return An error code: 0 - success, otherwise - failure
742 
743   @ref Advanced
744 **/
745 int CeedOperatorGetFieldByName(CeedOperator op, const char *field_name, CeedOperatorField *op_field) {
746   CeedInt            num_input_fields, num_output_fields;
747   CeedOperatorField *input_fields, *output_fields;
748   char              *name;
749   CeedCall(CeedOperatorGetFields(op, &num_input_fields, &input_fields, &num_output_fields, &output_fields));
750 
751   for (CeedInt i = 0; i < num_input_fields; i++) {
752     CeedCall(CeedOperatorFieldGetName(input_fields[i], &name));
753     if (!strcmp(name, field_name)) {
754       *op_field = input_fields[i];
755       return CEED_ERROR_SUCCESS;
756     }
757   }
758 
759   for (CeedInt i = 0; i < num_output_fields; i++) {
760     CeedCall(CeedOperatorFieldGetName(output_fields[i], &name));
761     if (!strcmp(name, field_name)) {
762       *op_field = output_fields[i];
763       return CEED_ERROR_SUCCESS;
764     }
765   }
766 
767   bool has_name = op->name;
768   // LCOV_EXCL_START
769   return CeedError(op->ceed, CEED_ERROR_MINOR, "The field \"%s\" not found in CeedOperator%s%s%s.\n", field_name, has_name ? " \"" : "",
770                    has_name ? op->name : "", has_name ? "\"" : "");
771   // LCOV_EXCL_STOP
772 }
773 
774 /**
775   @brief Get the name of a CeedOperatorField
776 
777   @param[in]  op_field    CeedOperatorField
778   @param[out] field_name  Variable to store the field name
779 
780   @return An error code: 0 - success, otherwise - failure
781 
782   @ref Advanced
783 **/
784 int CeedOperatorFieldGetName(CeedOperatorField op_field, char **field_name) {
785   *field_name = (char *)op_field->field_name;
786   return CEED_ERROR_SUCCESS;
787 }
788 
789 /**
790   @brief Get the CeedElemRestriction of a CeedOperatorField
791 
792   @param[in]  op_field CeedOperatorField
793   @param[out] rstr     Variable to store CeedElemRestriction
794 
795   @return An error code: 0 - success, otherwise - failure
796 
797   @ref Advanced
798 **/
799 int CeedOperatorFieldGetElemRestriction(CeedOperatorField op_field, CeedElemRestriction *rstr) {
800   *rstr = op_field->elem_rstr;
801   return CEED_ERROR_SUCCESS;
802 }
803 
804 /**
805   @brief Get the CeedBasis of a CeedOperatorField
806 
807   @param[in]  op_field CeedOperatorField
808   @param[out] basis    Variable to store CeedBasis
809 
810   @return An error code: 0 - success, otherwise - failure
811 
812   @ref Advanced
813 **/
814 int CeedOperatorFieldGetBasis(CeedOperatorField op_field, CeedBasis *basis) {
815   *basis = op_field->basis;
816   return CEED_ERROR_SUCCESS;
817 }
818 
819 /**
820   @brief Get the CeedVector of a CeedOperatorField
821 
822   @param[in]  op_field CeedOperatorField
823   @param[out] vec      Variable to store CeedVector
824 
825   @return An error code: 0 - success, otherwise - failure
826 
827   @ref Advanced
828 **/
829 int CeedOperatorFieldGetVector(CeedOperatorField op_field, CeedVector *vec) {
830   *vec = op_field->vec;
831   return CEED_ERROR_SUCCESS;
832 }
833 
834 /**
835   @brief Add a sub-operator to a composite CeedOperator
836 
837   @param[in,out] composite_op Composite CeedOperator
838   @param[in]     sub_op       Sub-operator CeedOperator
839 
840   @return An error code: 0 - success, otherwise - failure
841 
842   @ref User
843  */
844 int CeedCompositeOperatorAddSub(CeedOperator composite_op, CeedOperator sub_op) {
845   CeedCheck(composite_op->is_composite, composite_op->ceed, CEED_ERROR_MINOR, "CeedOperator is not a composite operator");
846   CeedCheck(composite_op->num_suboperators < CEED_COMPOSITE_MAX, composite_op->ceed, CEED_ERROR_UNSUPPORTED, "Cannot add additional sub-operators");
847   CeedCheck(!composite_op->is_immutable, composite_op->ceed, CEED_ERROR_MAJOR, "Operator cannot be changed after set as immutable");
848 
849   {
850     CeedSize input_size, output_size;
851     CeedCall(CeedOperatorGetActiveVectorLengths(sub_op, &input_size, &output_size));
852     if (composite_op->input_size == -1) composite_op->input_size = input_size;
853     if (composite_op->output_size == -1) composite_op->output_size = output_size;
854     // Note, a size of -1 means no active vector restriction set, so no incompatibility
855     CeedCheck((input_size == -1 || input_size == composite_op->input_size) && (output_size == -1 || output_size == composite_op->output_size),
856               composite_op->ceed, CEED_ERROR_MAJOR,
857               "Sub-operators must have compatible dimensions; composite operator of shape (%td, %td) not compatible with sub-operator of "
858               "shape (%td, %td)",
859               composite_op->input_size, composite_op->output_size, input_size, output_size);
860   }
861 
862   composite_op->sub_operators[composite_op->num_suboperators] = sub_op;
863   CeedCall(CeedOperatorReference(sub_op));
864   composite_op->num_suboperators++;
865   return CEED_ERROR_SUCCESS;
866 }
867 
868 /**
869   @brief Get the number of sub_operators associated with a CeedOperator
870 
871   @param[in]  op               CeedOperator
872   @param[out] num_suboperators Variable to store number of sub_operators
873 
874   @return An error code: 0 - success, otherwise - failure
875 
876   @ref Backend
877 **/
878 int CeedCompositeOperatorGetNumSub(CeedOperator op, CeedInt *num_suboperators) {
879   CeedCheck(op->is_composite, op->ceed, CEED_ERROR_MINOR, "Only defined for a composite operator");
880   *num_suboperators = op->num_suboperators;
881   return CEED_ERROR_SUCCESS;
882 }
883 
884 /**
885   @brief Get the list of sub_operators associated with a CeedOperator
886 
887   @param op                  CeedOperator
888   @param[out] sub_operators  Variable to store list of sub_operators
889 
890   @return An error code: 0 - success, otherwise - failure
891 
892   @ref Backend
893 **/
894 int CeedCompositeOperatorGetSubList(CeedOperator op, CeedOperator **sub_operators) {
895   CeedCheck(op->is_composite, op->ceed, CEED_ERROR_MINOR, "Only defined for a composite operator");
896   *sub_operators = op->sub_operators;
897   return CEED_ERROR_SUCCESS;
898 }
899 
900 /**
901   @brief Check if a CeedOperator is ready to be used.
902 
903   @param[in] op CeedOperator to check
904 
905   @return An error code: 0 - success, otherwise - failure
906 
907   @ref User
908 **/
909 int CeedOperatorCheckReady(CeedOperator op) {
910   Ceed ceed;
911   CeedCall(CeedOperatorGetCeed(op, &ceed));
912 
913   if (op->is_interface_setup) return CEED_ERROR_SUCCESS;
914 
915   CeedQFunction qf = op->qf;
916   if (op->is_composite) {
917     if (!op->num_suboperators) {
918       // Empty operator setup
919       op->input_size  = 0;
920       op->output_size = 0;
921     } else {
922       for (CeedInt i = 0; i < op->num_suboperators; i++) {
923         CeedCall(CeedOperatorCheckReady(op->sub_operators[i]));
924       }
925       // Sub-operators could be modified after adding to composite operator
926       // Need to verify no lvec incompatibility from any changes
927       CeedSize input_size, output_size;
928       CeedCall(CeedOperatorGetActiveVectorLengths(op, &input_size, &output_size));
929     }
930   } else {
931     CeedCheck(op->num_fields > 0, ceed, CEED_ERROR_INCOMPLETE, "No operator fields set");
932     CeedCheck(op->num_fields == qf->num_input_fields + qf->num_output_fields, ceed, CEED_ERROR_INCOMPLETE, "Not all operator fields set");
933     CeedCheck(op->has_restriction, ceed, CEED_ERROR_INCOMPLETE, "At least one restriction required");
934     CeedCheck(op->num_qpts > 0, ceed, CEED_ERROR_INCOMPLETE,
935               "At least one non-collocated basis is required or the number of quadrature points must be set");
936   }
937 
938   // Flag as immutable and ready
939   op->is_interface_setup = true;
940   if (op->qf && op->qf != CEED_QFUNCTION_NONE) op->qf->is_immutable = true;
941   if (op->dqf && op->dqf != CEED_QFUNCTION_NONE) op->dqf->is_immutable = true;
942   if (op->dqfT && op->dqfT != CEED_QFUNCTION_NONE) op->dqfT->is_immutable = true;
943   return CEED_ERROR_SUCCESS;
944 }
945 
946 /**
947   @brief Get vector lengths for the active input and/or output vectors of a CeedOperator.
948 
949   Note: Lengths of -1 indicate that the CeedOperator does not have an active input and/or output.
950 
951   @param[in]  op          CeedOperator
952   @param[out] input_size  Variable to store active input vector length, or NULL
953   @param[out] output_size Variable to store active output vector length, or NULL
954 
955   @return An error code: 0 - success, otherwise - failure
956 
957   @ref User
958 **/
959 int CeedOperatorGetActiveVectorLengths(CeedOperator op, CeedSize *input_size, CeedSize *output_size) {
960   bool is_composite;
961 
962   if (input_size) *input_size = op->input_size;
963   if (output_size) *output_size = op->output_size;
964 
965   CeedCall(CeedOperatorIsComposite(op, &is_composite));
966   if (is_composite && (op->input_size == -1 || op->output_size == -1)) {
967     for (CeedInt i = 0; i < op->num_suboperators; i++) {
968       CeedSize sub_input_size, sub_output_size;
969       CeedCall(CeedOperatorGetActiveVectorLengths(op->sub_operators[i], &sub_input_size, &sub_output_size));
970       if (op->input_size == -1) op->input_size = sub_input_size;
971       if (op->output_size == -1) op->output_size = sub_output_size;
972       // Note, a size of -1 means no active vector restriction set, so no incompatibility
973       CeedCheck((sub_input_size == -1 || sub_input_size == op->input_size) && (sub_output_size == -1 || sub_output_size == op->output_size), op->ceed,
974                 CEED_ERROR_MAJOR,
975                 "Sub-operators must have compatible dimensions; composite operator of shape (%td, %td) not compatible with sub-operator of "
976                 "shape (%td, %td)",
977                 op->input_size, op->output_size, input_size, output_size);
978     }
979   }
980 
981   return CEED_ERROR_SUCCESS;
982 }
983 
984 /**
985   @brief Set reuse of CeedQFunction data in CeedOperatorLinearAssemble* functions.
986 
987   When `reuse_assembly_data = false` (default), the CeedQFunction associated with this CeedOperator is re-assembled every time a
988 `CeedOperatorLinearAssemble*` function is called.
989   When `reuse_assembly_data = true`, the CeedQFunction associated with this CeedOperator is reused between calls to
990 `CeedOperatorSetQFunctionAssemblyDataUpdated`.
991 
992   @param[in] op                  CeedOperator
993   @param[in] reuse_assembly_data Boolean flag setting assembly data reuse
994 
995   @return An error code: 0 - success, otherwise - failure
996 
997   @ref Advanced
998 **/
999 int CeedOperatorSetQFunctionAssemblyReuse(CeedOperator op, bool reuse_assembly_data) {
1000   bool is_composite;
1001 
1002   CeedCall(CeedOperatorIsComposite(op, &is_composite));
1003   if (is_composite) {
1004     for (CeedInt i = 0; i < op->num_suboperators; i++) {
1005       CeedCall(CeedOperatorSetQFunctionAssemblyReuse(op->sub_operators[i], reuse_assembly_data));
1006     }
1007   } else {
1008     CeedCall(CeedQFunctionAssemblyDataSetReuse(op->qf_assembled, reuse_assembly_data));
1009   }
1010 
1011   return CEED_ERROR_SUCCESS;
1012 }
1013 
1014 /**
1015   @brief Mark CeedQFunction data as updated and the CeedQFunction as requiring re-assembly.
1016 
1017   @param[in] op                CeedOperator
1018   @param[in] needs_data_update Boolean flag setting assembly data reuse
1019 
1020   @return An error code: 0 - success, otherwise - failure
1021 
1022   @ref Advanced
1023 **/
1024 int CeedOperatorSetQFunctionAssemblyDataUpdateNeeded(CeedOperator op, bool needs_data_update) {
1025   bool is_composite;
1026 
1027   CeedCall(CeedOperatorIsComposite(op, &is_composite));
1028   if (is_composite) {
1029     for (CeedInt i = 0; i < op->num_suboperators; i++) {
1030       CeedCall(CeedOperatorSetQFunctionAssemblyDataUpdateNeeded(op->sub_operators[i], needs_data_update));
1031     }
1032   } else {
1033     CeedCall(CeedQFunctionAssemblyDataSetUpdateNeeded(op->qf_assembled, needs_data_update));
1034   }
1035 
1036   return CEED_ERROR_SUCCESS;
1037 }
1038 
1039 /**
1040   @brief Set the number of quadrature points associated with a CeedOperator.
1041 
1042   This should be used when creating a CeedOperator where every field has a collocated basis.
1043   This function cannot be used for composite CeedOperators.
1044 
1045   @param[in,out] op       CeedOperator
1046   @param[in]     num_qpts Number of quadrature points to set
1047 
1048   @return An error code: 0 - success, otherwise - failure
1049 
1050   @ref Advanced
1051 **/
1052 int CeedOperatorSetNumQuadraturePoints(CeedOperator op, CeedInt num_qpts) {
1053   CeedCheck(!op->is_composite, op->ceed, CEED_ERROR_MINOR, "Not defined for composite operator");
1054   CeedCheck(op->num_qpts == 0, op->ceed, CEED_ERROR_MINOR, "Number of quadrature points already defined");
1055   CeedCheck(!op->is_immutable, op->ceed, CEED_ERROR_MAJOR, "Operator cannot be changed after set as immutable");
1056   op->num_qpts = num_qpts;
1057   return CEED_ERROR_SUCCESS;
1058 }
1059 
1060 /**
1061   @brief Set name of CeedOperator for CeedOperatorView output
1062 
1063   @param[in,out] op   CeedOperator
1064   @param[in]     name Name to set, or NULL to remove previously set name
1065 
1066   @return An error code: 0 - success, otherwise - failure
1067 
1068   @ref User
1069 **/
1070 int CeedOperatorSetName(CeedOperator op, const char *name) {
1071   char  *name_copy;
1072   size_t name_len = name ? strlen(name) : 0;
1073 
1074   CeedCall(CeedFree(&op->name));
1075   if (name_len > 0) {
1076     CeedCall(CeedCalloc(name_len + 1, &name_copy));
1077     memcpy(name_copy, name, name_len);
1078     op->name = name_copy;
1079   }
1080 
1081   return CEED_ERROR_SUCCESS;
1082 }
1083 
1084 /**
1085   @brief View a CeedOperator
1086 
1087   @param[in] op     CeedOperator to view
1088   @param[in] stream Stream to write; typically stdout/stderr or a file
1089 
1090   @return Error code: 0 - success, otherwise - failure
1091 
1092   @ref User
1093 **/
1094 int CeedOperatorView(CeedOperator op, FILE *stream) {
1095   bool has_name = op->name;
1096 
1097   if (op->is_composite) {
1098     fprintf(stream, "Composite CeedOperator%s%s\n", has_name ? " - " : "", has_name ? op->name : "");
1099 
1100     for (CeedInt i = 0; i < op->num_suboperators; i++) {
1101       has_name = op->sub_operators[i]->name;
1102       fprintf(stream, "  SubOperator %" CeedInt_FMT "%s%s:\n", i, has_name ? " - " : "", has_name ? op->sub_operators[i]->name : "");
1103       CeedCall(CeedOperatorSingleView(op->sub_operators[i], 1, stream));
1104     }
1105   } else {
1106     fprintf(stream, "CeedOperator%s%s\n", has_name ? " - " : "", has_name ? op->name : "");
1107     CeedCall(CeedOperatorSingleView(op, 0, stream));
1108   }
1109   return CEED_ERROR_SUCCESS;
1110 }
1111 
1112 /**
1113   @brief Get the Ceed associated with a CeedOperator
1114 
1115   @param[in]  op   CeedOperator
1116   @param[out] ceed Variable to store Ceed
1117 
1118   @return An error code: 0 - success, otherwise - failure
1119 
1120   @ref Advanced
1121 **/
1122 int CeedOperatorGetCeed(CeedOperator op, Ceed *ceed) {
1123   *ceed = op->ceed;
1124   return CEED_ERROR_SUCCESS;
1125 }
1126 
1127 /**
1128   @brief Get the number of elements associated with a CeedOperator
1129 
1130   @param[in]  op       CeedOperator
1131   @param[out] num_elem Variable to store number of elements
1132 
1133   @return An error code: 0 - success, otherwise - failure
1134 
1135   @ref Advanced
1136 **/
1137 int CeedOperatorGetNumElements(CeedOperator op, CeedInt *num_elem) {
1138   CeedCheck(!op->is_composite, op->ceed, CEED_ERROR_MINOR, "Not defined for composite operator");
1139   *num_elem = op->num_elem;
1140   return CEED_ERROR_SUCCESS;
1141 }
1142 
1143 /**
1144   @brief Get the number of quadrature points associated with a CeedOperator
1145 
1146   @param[in]  op       CeedOperator
1147   @param[out] num_qpts Variable to store vector number of quadrature points
1148 
1149   @return An error code: 0 - success, otherwise - failure
1150 
1151   @ref Advanced
1152 **/
1153 int CeedOperatorGetNumQuadraturePoints(CeedOperator op, CeedInt *num_qpts) {
1154   CeedCheck(!op->is_composite, op->ceed, CEED_ERROR_MINOR, "Not defined for composite operator");
1155   *num_qpts = op->num_qpts;
1156   return CEED_ERROR_SUCCESS;
1157 }
1158 
1159 /**
1160   @brief Estimate number of FLOPs required to apply CeedOperator on the active vector
1161 
1162   @param[in]  op    CeedOperator to estimate FLOPs for
1163   @param[out] flops Address of variable to hold FLOPs estimate
1164 
1165   @ref Backend
1166 **/
1167 int CeedOperatorGetFlopsEstimate(CeedOperator op, CeedSize *flops) {
1168   bool is_composite;
1169   CeedCall(CeedOperatorCheckReady(op));
1170 
1171   *flops = 0;
1172   CeedCall(CeedOperatorIsComposite(op, &is_composite));
1173   if (is_composite) {
1174     CeedInt num_suboperators;
1175     CeedCall(CeedCompositeOperatorGetNumSub(op, &num_suboperators));
1176     CeedOperator *sub_operators;
1177     CeedCall(CeedCompositeOperatorGetSubList(op, &sub_operators));
1178 
1179     // FLOPs for each suboperator
1180     for (CeedInt i = 0; i < num_suboperators; i++) {
1181       CeedSize suboperator_flops;
1182       CeedCall(CeedOperatorGetFlopsEstimate(sub_operators[i], &suboperator_flops));
1183       *flops += suboperator_flops;
1184     }
1185   } else {
1186     CeedInt            num_input_fields, num_output_fields;
1187     CeedOperatorField *input_fields, *output_fields;
1188     CeedCall(CeedOperatorGetFields(op, &num_input_fields, &input_fields, &num_output_fields, &output_fields));
1189     CeedInt num_elem = 0;
1190     CeedCall(CeedOperatorGetNumElements(op, &num_elem));
1191 
1192     // Input FLOPs
1193     for (CeedInt i = 0; i < num_input_fields; i++) {
1194       if (input_fields[i]->vec == CEED_VECTOR_ACTIVE) {
1195         CeedSize restr_flops, basis_flops;
1196 
1197         CeedCall(CeedElemRestrictionGetFlopsEstimate(input_fields[i]->elem_rstr, CEED_NOTRANSPOSE, &restr_flops));
1198         *flops += restr_flops;
1199         CeedCall(CeedBasisGetFlopsEstimate(input_fields[i]->basis, CEED_NOTRANSPOSE, op->qf->input_fields[i]->eval_mode, &basis_flops));
1200         *flops += basis_flops * num_elem;
1201       }
1202     }
1203     // QF FLOPs
1204     CeedInt  num_qpts;
1205     CeedSize qf_flops;
1206     CeedCall(CeedOperatorGetNumQuadraturePoints(op, &num_qpts));
1207     CeedCall(CeedQFunctionGetFlopsEstimate(op->qf, &qf_flops));
1208     *flops += num_elem * num_qpts * qf_flops;
1209     // Output FLOPs
1210     for (CeedInt i = 0; i < num_output_fields; i++) {
1211       if (output_fields[i]->vec == CEED_VECTOR_ACTIVE) {
1212         CeedSize restr_flops, basis_flops;
1213 
1214         CeedCall(CeedElemRestrictionGetFlopsEstimate(output_fields[i]->elem_rstr, CEED_TRANSPOSE, &restr_flops));
1215         *flops += restr_flops;
1216         CeedCall(CeedBasisGetFlopsEstimate(output_fields[i]->basis, CEED_TRANSPOSE, op->qf->output_fields[i]->eval_mode, &basis_flops));
1217         *flops += basis_flops * num_elem;
1218       }
1219     }
1220   }
1221 
1222   return CEED_ERROR_SUCCESS;
1223 }
1224 
1225 /**
1226   @brief Get CeedQFunction global context for a CeedOperator.
1227 
1228   The caller is responsible for destroying `ctx` returned from this function via `CeedQFunctionContextDestroy()`.
1229 
1230   Note: If the value of `ctx` passed into this function is non-NULL, then it is assumed that `ctx` is a pointer to a CeedQFunctionContext.
1231         This CeedQFunctionContext will be destroyed if `ctx` is the only reference to this CeedQFunctionContext.
1232 
1233   @param[in]  op  CeedOperator
1234   @param[out] ctx Variable to store CeedQFunctionContext
1235 
1236   @return An error code: 0 - success, otherwise - failure
1237 
1238   @ref Advanced
1239 **/
1240 int CeedOperatorGetContext(CeedOperator op, CeedQFunctionContext *ctx) {
1241   CeedCheck(!op->is_composite, op->ceed, CEED_ERROR_INCOMPATIBLE, "Cannot retrieve QFunctionContext for composite operator");
1242   if (op->qf->ctx) CeedCall(CeedQFunctionContextReferenceCopy(op->qf->ctx, ctx));
1243   else *ctx = NULL;
1244   return CEED_ERROR_SUCCESS;
1245 }
1246 
1247 /**
1248   @brief Get label for a registered QFunctionContext field, or `NULL` if no field has been registered with this `field_name`.
1249 
1250   Fields are registered via `CeedQFunctionContextRegister*()` functions (eg. `CeedQFunctionContextRegisterDouble()`).
1251 
1252   @param[in]  op          CeedOperator
1253   @param[in]  field_name  Name of field to retrieve label
1254   @param[out] field_label Variable to field label
1255 
1256   @return An error code: 0 - success, otherwise - failure
1257 
1258   @ref User
1259 **/
1260 int CeedOperatorGetContextFieldLabel(CeedOperator op, const char *field_name, CeedContextFieldLabel *field_label) {
1261   bool is_composite;
1262   CeedCall(CeedOperatorIsComposite(op, &is_composite));
1263 
1264   if (is_composite) {
1265     // Composite operator
1266     // -- Check if composite label already created
1267     for (CeedInt i = 0; i < op->num_context_labels; i++) {
1268       if (!strcmp(op->context_labels[i]->name, field_name)) {
1269         *field_label = op->context_labels[i];
1270         return CEED_ERROR_SUCCESS;
1271       }
1272     }
1273 
1274     // -- Create composite label if needed
1275     CeedInt               num_sub;
1276     CeedOperator         *sub_operators;
1277     CeedContextFieldLabel new_field_label;
1278 
1279     CeedCall(CeedCalloc(1, &new_field_label));
1280     CeedCall(CeedCompositeOperatorGetNumSub(op, &num_sub));
1281     CeedCall(CeedCompositeOperatorGetSubList(op, &sub_operators));
1282     CeedCall(CeedCalloc(num_sub, &new_field_label->sub_labels));
1283     new_field_label->num_sub_labels = num_sub;
1284 
1285     bool field_found = false;
1286     for (CeedInt i = 0; i < num_sub; i++) {
1287       if (sub_operators[i]->qf->ctx) {
1288         CeedContextFieldLabel new_field_label_i;
1289         CeedCall(CeedQFunctionContextGetFieldLabel(sub_operators[i]->qf->ctx, field_name, &new_field_label_i));
1290         if (new_field_label_i) {
1291           field_found                    = true;
1292           new_field_label->sub_labels[i] = new_field_label_i;
1293           new_field_label->name          = new_field_label_i->name;
1294           new_field_label->description   = new_field_label_i->description;
1295           if (new_field_label->type && new_field_label->type != new_field_label_i->type) {
1296             // LCOV_EXCL_START
1297             CeedCall(CeedFree(&new_field_label));
1298             return CeedError(op->ceed, CEED_ERROR_INCOMPATIBLE, "Incompatible field types on sub-operator contexts. %s != %s",
1299                              CeedContextFieldTypes[new_field_label->type], CeedContextFieldTypes[new_field_label_i->type]);
1300             // LCOV_EXCL_STOP
1301           } else {
1302             new_field_label->type = new_field_label_i->type;
1303           }
1304           if (new_field_label->num_values != 0 && new_field_label->num_values != new_field_label_i->num_values) {
1305             // LCOV_EXCL_START
1306             CeedCall(CeedFree(&new_field_label));
1307             return CeedError(op->ceed, CEED_ERROR_INCOMPATIBLE, "Incompatible field number of values on sub-operator contexts. %ld != %ld",
1308                              new_field_label->num_values, new_field_label_i->num_values);
1309             // LCOV_EXCL_STOP
1310           } else {
1311             new_field_label->num_values = new_field_label_i->num_values;
1312           }
1313         }
1314       }
1315     }
1316     // -- Cleanup if field was found
1317     if (field_found) {
1318       *field_label = new_field_label;
1319     } else {
1320       // LCOV_EXCL_START
1321       CeedCall(CeedFree(&new_field_label->sub_labels));
1322       CeedCall(CeedFree(&new_field_label));
1323       *field_label = NULL;
1324       // LCOV_EXCL_STOP
1325     }
1326   } else {
1327     // Single, non-composite operator
1328     if (op->qf->ctx) {
1329       CeedCall(CeedQFunctionContextGetFieldLabel(op->qf->ctx, field_name, field_label));
1330     } else {
1331       *field_label = NULL;
1332     }
1333   }
1334 
1335   // Set label in operator
1336   if (*field_label) {
1337     (*field_label)->from_op = true;
1338 
1339     // Move new composite label to operator
1340     if (op->num_context_labels == 0) {
1341       CeedCall(CeedCalloc(1, &op->context_labels));
1342       op->max_context_labels = 1;
1343     } else if (op->num_context_labels == op->max_context_labels) {
1344       CeedCall(CeedRealloc(2 * op->num_context_labels, &op->context_labels));
1345       op->max_context_labels *= 2;
1346     }
1347     op->context_labels[op->num_context_labels] = *field_label;
1348     op->num_context_labels++;
1349   }
1350 
1351   return CEED_ERROR_SUCCESS;
1352 }
1353 
1354 /**
1355   @brief Set QFunctionContext field holding double precision values.
1356 
1357   For composite operators, the values are set in all sub-operator QFunctionContexts that have a matching `field_name`.
1358 
1359   @param[in,out] op          CeedOperator
1360   @param[in]     field_label Label of field to set
1361   @param[in]     values      Values to set
1362 
1363   @return An error code: 0 - success, otherwise - failure
1364 
1365   @ref User
1366 **/
1367 int CeedOperatorSetContextDouble(CeedOperator op, CeedContextFieldLabel field_label, double *values) {
1368   return CeedOperatorContextSetGeneric(op, field_label, CEED_CONTEXT_FIELD_DOUBLE, values);
1369 }
1370 
1371 /**
1372   @brief Get QFunctionContext field holding double precision values, read-only.
1373 
1374   For composite operators, the values correspond to the first sub-operator QFunctionContexts that has a matching `field_name`.
1375 
1376   @param[in]  op          CeedOperator
1377   @param[in]  field_label Label of field to get
1378   @param[out] num_values  Number of values in the field label
1379   @param[out] values      Pointer to context values
1380 
1381   @return An error code: 0 - success, otherwise - failure
1382 
1383   @ref User
1384 **/
1385 int CeedOperatorGetContextDoubleRead(CeedOperator op, CeedContextFieldLabel field_label, size_t *num_values, const double **values) {
1386   return CeedOperatorContextGetGenericRead(op, field_label, CEED_CONTEXT_FIELD_DOUBLE, num_values, values);
1387 }
1388 
1389 /**
1390   @brief Restore QFunctionContext field holding double precision values, read-only.
1391 
1392   @param[in]  op          CeedOperator
1393   @param[in]  field_label Label of field to restore
1394   @param[out] values      Pointer to context values
1395 
1396   @return An error code: 0 - success, otherwise - failure
1397 
1398   @ref User
1399 **/
1400 int CeedOperatorRestoreContextDoubleRead(CeedOperator op, CeedContextFieldLabel field_label, const double **values) {
1401   return CeedOperatorContextRestoreGenericRead(op, field_label, CEED_CONTEXT_FIELD_DOUBLE, values);
1402 }
1403 
1404 /**
1405   @brief Set QFunctionContext field holding int32 values.
1406 
1407   For composite operators, the values are set in all sub-operator QFunctionContexts that have a matching `field_name`.
1408 
1409   @param[in,out] op          CeedOperator
1410   @param[in]     field_label Label of field to set
1411   @param[in]     values      Values to set
1412 
1413   @return An error code: 0 - success, otherwise - failure
1414 
1415   @ref User
1416 **/
1417 int CeedOperatorSetContextInt32(CeedOperator op, CeedContextFieldLabel field_label, int *values) {
1418   return CeedOperatorContextSetGeneric(op, field_label, CEED_CONTEXT_FIELD_INT32, values);
1419 }
1420 
1421 /**
1422   @brief Get QFunctionContext field holding int32 values, read-only.
1423 
1424   For composite operators, the values correspond to the first sub-operator QFunctionContexts that has a matching `field_name`.
1425 
1426   @param[in]  op          CeedOperator
1427   @param[in]  field_label Label of field to get
1428   @param[out] num_values  Number of int32 values in `values`
1429   @param[out] values      Pointer to context values
1430 
1431   @return An error code: 0 - success, otherwise - failure
1432 
1433   @ref User
1434 **/
1435 int CeedOperatorGetContextInt32Read(CeedOperator op, CeedContextFieldLabel field_label, size_t *num_values, const int **values) {
1436   return CeedOperatorContextGetGenericRead(op, field_label, CEED_CONTEXT_FIELD_INT32, num_values, values);
1437 }
1438 
1439 /**
1440   @brief Restore QFunctionContext field holding int32 values, read-only.
1441 
1442   @param[in]  op          CeedOperator
1443   @param[in]  field_label Label of field to get
1444   @param[out] values      Pointer to context values
1445 
1446   @return An error code: 0 - success, otherwise - failure
1447 
1448   @ref User
1449 **/
1450 int CeedOperatorRestoreContextInt32Read(CeedOperator op, CeedContextFieldLabel field_label, const int **values) {
1451   return CeedOperatorContextRestoreGenericRead(op, field_label, CEED_CONTEXT_FIELD_INT32, values);
1452 }
1453 
1454 /**
1455   @brief Apply CeedOperator to a vector
1456 
1457   This computes the action of the operator on the specified (active) input, yielding its (active) output.
1458   All inputs and outputs must be specified using CeedOperatorSetField().
1459 
1460   Note: Calling this function asserts that setup is complete and sets the CeedOperator as immutable.
1461 
1462   @param[in]  op      CeedOperator to apply
1463   @param[in]  in      CeedVector containing input state or @ref CEED_VECTOR_NONE if there are no active inputs
1464   @param[out] out     CeedVector to store result of applying operator (must be distinct from @a in) or @ref CEED_VECTOR_NONE if there are no
1465 active outputs
1466   @param[in]  request Address of CeedRequest for non-blocking completion, else @ref CEED_REQUEST_IMMEDIATE
1467 
1468   @return An error code: 0 - success, otherwise - failure
1469 
1470   @ref User
1471 **/
1472 int CeedOperatorApply(CeedOperator op, CeedVector in, CeedVector out, CeedRequest *request) {
1473   CeedCall(CeedOperatorCheckReady(op));
1474 
1475   if (op->is_composite) {
1476     // Composite Operator
1477     if (op->ApplyComposite) {
1478       CeedCall(op->ApplyComposite(op, in, out, request));
1479     } else {
1480       CeedInt num_suboperators;
1481       CeedCall(CeedCompositeOperatorGetNumSub(op, &num_suboperators));
1482       CeedOperator *sub_operators;
1483       CeedCall(CeedCompositeOperatorGetSubList(op, &sub_operators));
1484 
1485       // Zero all output vectors
1486       if (out != CEED_VECTOR_NONE) CeedCall(CeedVectorSetValue(out, 0.0));
1487       for (CeedInt i = 0; i < num_suboperators; i++) {
1488         for (CeedInt j = 0; j < sub_operators[i]->qf->num_output_fields; j++) {
1489           CeedVector vec = sub_operators[i]->output_fields[j]->vec;
1490           if (vec != CEED_VECTOR_ACTIVE && vec != CEED_VECTOR_NONE) {
1491             CeedCall(CeedVectorSetValue(vec, 0.0));
1492           }
1493         }
1494       }
1495       // Apply
1496       for (CeedInt i = 0; i < op->num_suboperators; i++) {
1497         CeedCall(CeedOperatorApplyAdd(op->sub_operators[i], in, out, request));
1498       }
1499     }
1500   } else {
1501     // Standard Operator
1502     if (op->Apply) {
1503       CeedCall(op->Apply(op, in, out, request));
1504     } else {
1505       // Zero all output vectors
1506       CeedQFunction qf = op->qf;
1507       for (CeedInt i = 0; i < qf->num_output_fields; i++) {
1508         CeedVector vec = op->output_fields[i]->vec;
1509         if (vec == CEED_VECTOR_ACTIVE) vec = out;
1510         if (vec != CEED_VECTOR_NONE) CeedCall(CeedVectorSetValue(vec, 0.0));
1511       }
1512       // Apply
1513       if (op->num_elem) CeedCall(op->ApplyAdd(op, in, out, request));
1514     }
1515   }
1516   return CEED_ERROR_SUCCESS;
1517 }
1518 
1519 /**
1520   @brief Apply CeedOperator to a vector and add result to output vector
1521 
1522   This computes the action of the operator on the specified (active) input, yielding its (active) output.
1523   All inputs and outputs must be specified using CeedOperatorSetField().
1524 
1525   @param[in]  op      CeedOperator to apply
1526   @param[in]  in      CeedVector containing input state or NULL if there are no active inputs
1527   @param[out] out     CeedVector to sum in result of applying operator (must be distinct from @a in) or NULL if there are no active outputs
1528   @param[in]  request Address of CeedRequest for non-blocking completion, else @ref CEED_REQUEST_IMMEDIATE
1529 
1530   @return An error code: 0 - success, otherwise - failure
1531 
1532   @ref User
1533 **/
1534 int CeedOperatorApplyAdd(CeedOperator op, CeedVector in, CeedVector out, CeedRequest *request) {
1535   CeedCall(CeedOperatorCheckReady(op));
1536 
1537   if (op->is_composite) {
1538     // Composite Operator
1539     if (op->ApplyAddComposite) {
1540       CeedCall(op->ApplyAddComposite(op, in, out, request));
1541     } else {
1542       CeedInt num_suboperators;
1543       CeedCall(CeedCompositeOperatorGetNumSub(op, &num_suboperators));
1544       CeedOperator *sub_operators;
1545       CeedCall(CeedCompositeOperatorGetSubList(op, &sub_operators));
1546 
1547       for (CeedInt i = 0; i < num_suboperators; i++) {
1548         CeedCall(CeedOperatorApplyAdd(sub_operators[i], in, out, request));
1549       }
1550     }
1551   } else if (op->num_elem) {
1552     // Standard Operator
1553     CeedCall(op->ApplyAdd(op, in, out, request));
1554   }
1555   return CEED_ERROR_SUCCESS;
1556 }
1557 
1558 /**
1559   @brief Destroy a CeedOperator
1560 
1561   @param[in,out] op CeedOperator to destroy
1562 
1563   @return An error code: 0 - success, otherwise - failure
1564 
1565   @ref User
1566 **/
1567 int CeedOperatorDestroy(CeedOperator *op) {
1568   if (!*op || --(*op)->ref_count > 0) {
1569     *op = NULL;
1570     return CEED_ERROR_SUCCESS;
1571   }
1572   if ((*op)->Destroy) CeedCall((*op)->Destroy(*op));
1573   CeedCall(CeedDestroy(&(*op)->ceed));
1574   // Free fields
1575   for (CeedInt i = 0; i < (*op)->num_fields; i++) {
1576     if ((*op)->input_fields[i]) {
1577       if ((*op)->input_fields[i]->elem_rstr != CEED_ELEMRESTRICTION_NONE) {
1578         CeedCall(CeedElemRestrictionDestroy(&(*op)->input_fields[i]->elem_rstr));
1579       }
1580       if ((*op)->input_fields[i]->basis != CEED_BASIS_COLLOCATED) {
1581         CeedCall(CeedBasisDestroy(&(*op)->input_fields[i]->basis));
1582       }
1583       if ((*op)->input_fields[i]->vec != CEED_VECTOR_ACTIVE && (*op)->input_fields[i]->vec != CEED_VECTOR_NONE) {
1584         CeedCall(CeedVectorDestroy(&(*op)->input_fields[i]->vec));
1585       }
1586       CeedCall(CeedFree(&(*op)->input_fields[i]->field_name));
1587       CeedCall(CeedFree(&(*op)->input_fields[i]));
1588     }
1589   }
1590   for (CeedInt i = 0; i < (*op)->num_fields; i++) {
1591     if ((*op)->output_fields[i]) {
1592       CeedCall(CeedElemRestrictionDestroy(&(*op)->output_fields[i]->elem_rstr));
1593       if ((*op)->output_fields[i]->basis != CEED_BASIS_COLLOCATED) {
1594         CeedCall(CeedBasisDestroy(&(*op)->output_fields[i]->basis));
1595       }
1596       if ((*op)->output_fields[i]->vec != CEED_VECTOR_ACTIVE && (*op)->output_fields[i]->vec != CEED_VECTOR_NONE) {
1597         CeedCall(CeedVectorDestroy(&(*op)->output_fields[i]->vec));
1598       }
1599       CeedCall(CeedFree(&(*op)->output_fields[i]->field_name));
1600       CeedCall(CeedFree(&(*op)->output_fields[i]));
1601     }
1602   }
1603   // Destroy sub_operators
1604   for (CeedInt i = 0; i < (*op)->num_suboperators; i++) {
1605     if ((*op)->sub_operators[i]) {
1606       CeedCall(CeedOperatorDestroy(&(*op)->sub_operators[i]));
1607     }
1608   }
1609   CeedCall(CeedQFunctionDestroy(&(*op)->qf));
1610   CeedCall(CeedQFunctionDestroy(&(*op)->dqf));
1611   CeedCall(CeedQFunctionDestroy(&(*op)->dqfT));
1612   // Destroy any composite labels
1613   if ((*op)->is_composite) {
1614     for (CeedInt i = 0; i < (*op)->num_context_labels; i++) {
1615       CeedCall(CeedFree(&(*op)->context_labels[i]->sub_labels));
1616       CeedCall(CeedFree(&(*op)->context_labels[i]));
1617     }
1618   }
1619   CeedCall(CeedFree(&(*op)->context_labels));
1620 
1621   // Destroy fallback
1622   CeedCall(CeedOperatorDestroy(&(*op)->op_fallback));
1623 
1624   // Destroy assembly data
1625   CeedCall(CeedQFunctionAssemblyDataDestroy(&(*op)->qf_assembled));
1626   CeedCall(CeedOperatorAssemblyDataDestroy(&(*op)->op_assembled));
1627 
1628   CeedCall(CeedFree(&(*op)->input_fields));
1629   CeedCall(CeedFree(&(*op)->output_fields));
1630   CeedCall(CeedFree(&(*op)->sub_operators));
1631   CeedCall(CeedFree(&(*op)->name));
1632   CeedCall(CeedFree(op));
1633   return CEED_ERROR_SUCCESS;
1634 }
1635 
1636 /// @}
1637