xref: /honee/src/mat-ceed.c (revision 7ecf6641c340caefd7fa9f7fc7a6efebf89ae19d)
1 // SPDX-FileCopyrightText: Copyright (c) 2017-2024, HONEE contributors.
2 // SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause
3 /// @file
4 /// MatCEED implementation
5 
6 #include <ceed.h>
7 #include <ceed/backend.h>
8 #include <mat-ceed-impl.h>
9 #include <mat-ceed.h>
10 #include <petsc-ceed-utils.h>
11 #include <petsc-ceed.h>
12 #include <petscdm.h>
13 #include <petscmat.h>
14 #include <stdbool.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 
19 PetscClassId  MATCEED_CLASSID;
20 PetscLogEvent MATCEED_MULT, MATCEED_MULT_CEEDOP, MATCEED_MULT_TRANSPOSE, MATCEED_MULT_TRANSPOSE_CEEDOP, MATCEED_ASSEMBLE_DIAGONAL,
21     MATCEED_ASSEMBLE_DIAGONAL_CEEDOP, MATCEED_SETUP_PBDIAGONAL, MATCEED_SETUP_PBDIAGONAL_CEEDOP, MATCEED_ASSEMBLE_PBDIAGONAL,
22     MATCEED_ASSEMBLE_PBDIAGONAL_CEEDOP, MATCEED_SETUP_FULL, MATCEED_SETUP_FULL_CEEDOP, MATCEED_ASSEMBLE_FULL, MATCEED_ASSEMBLE_FULL_CEEDOP;
23 
24 /**
25   @brief Register MATCEED log events.
26 
27   Not collective across MPI processes.
28 
29   @return An error code: 0 - success, otherwise - failure
30 **/
31 static PetscErrorCode MatCeedRegisterLogEvents() {
32   static PetscBool registered = PETSC_FALSE;
33 
34   PetscFunctionBeginUser;
35   if (registered) PetscFunctionReturn(PETSC_SUCCESS);
36   PetscCall(PetscClassIdRegister("MatCEED", &MATCEED_CLASSID));
37   PetscCall(PetscLogEventRegister("MatCEEDMul", MATCEED_CLASSID, &MATCEED_MULT));
38   PetscCall(PetscLogEventRegister("MatCEEDMulCeed", MATCEED_CLASSID, &MATCEED_MULT_CEEDOP));
39   PetscCall(PetscLogEventRegister("MatCEEDMulT", MATCEED_CLASSID, &MATCEED_MULT_TRANSPOSE));
40   PetscCall(PetscLogEventRegister("MatCEEDMulTCeed", MATCEED_CLASSID, &MATCEED_MULT_TRANSPOSE_CEEDOP));
41   PetscCall(PetscLogEventRegister("MatCEEDAsmDiag", MATCEED_CLASSID, &MATCEED_ASSEMBLE_DIAGONAL));
42   PetscCall(PetscLogEventRegister("MatCEEDAsmPBDSU", MATCEED_CLASSID, &MATCEED_SETUP_PBDIAGONAL));
43   PetscCall(PetscLogEventRegister("MatCEEDAsmPBDSUCeed", MATCEED_CLASSID, &MATCEED_SETUP_PBDIAGONAL_CEEDOP));
44   PetscCall(PetscLogEventRegister("MatCEEDAsmPBD", MATCEED_CLASSID, &MATCEED_ASSEMBLE_PBDIAGONAL));
45   PetscCall(PetscLogEventRegister("MatCEEDAsmPBDCeed", MATCEED_CLASSID, &MATCEED_ASSEMBLE_PBDIAGONAL_CEEDOP));
46   PetscCall(PetscLogEventRegister("MatCEEDAsmSU", MATCEED_CLASSID, &MATCEED_SETUP_FULL));
47   PetscCall(PetscLogEventRegister("MatCEEDAsmSUCeed", MATCEED_CLASSID, &MATCEED_SETUP_FULL_CEEDOP));
48   PetscCall(PetscLogEventRegister("MatCEEDAsm", MATCEED_CLASSID, &MATCEED_ASSEMBLE_FULL));
49   PetscCall(PetscLogEventRegister("MatCEEDAsmCeed", MATCEED_CLASSID, &MATCEED_ASSEMBLE_FULL_CEEDOP));
50   registered = PETSC_TRUE;
51   PetscFunctionReturn(PETSC_SUCCESS);
52 }
53 
54 /**
55   @brief Assemble the point block diagonal of a `MATCEED` into a `MATAIJ` or similar.
56          The `mat_coo` preallocation is set to match the sparsity pattern of `mat_ceed`.
57          The caller is responsible for assuring the global and local sizes are compatible, otherwise this function will fail.
58 
59   Collective across MPI processes.
60 
61   @param[in]      mat_ceed  `MATCEED` to assemble
62   @param[in,out]  mat_coo   `MATAIJ` or similar to assemble into
63 
64   @return An error code: 0 - success, otherwise - failure
65 **/
66 static PetscErrorCode MatCeedAssemblePointBlockDiagonalCOO(Mat mat_ceed, Mat mat_coo) {
67   MatCeedContext ctx;
68 
69   PetscFunctionBeginUser;
70   PetscCall(MatShellGetContext(mat_ceed, &ctx));
71 
72   // Check if COO pattern set
73   {
74     PetscInt index = -1;
75 
76     for (PetscInt i = 0; i < ctx->num_mats_assembled_pbd; i++) {
77       if (ctx->mats_assembled_pbd[i] == mat_coo) index = i;
78     }
79     if (index == -1) {
80       PetscInt     *rows_petsc = NULL, *cols_petsc = NULL;
81       CeedInt      *rows_ceed, *cols_ceed;
82       PetscCount    num_entries;
83       PetscLogStage stage_amg_setup;
84 
85       // -- Assemble sparsity pattern if mat hasn't been assembled before
86       PetscCall(PetscLogStageGetId("MatCEED Asm Setup", &stage_amg_setup));
87       if (stage_amg_setup == -1) {
88         PetscCall(PetscLogStageRegister("MatCEED Asm Setup", &stage_amg_setup));
89       }
90       PetscCall(PetscLogStagePush(stage_amg_setup));
91       PetscCall(PetscLogEventBegin(MATCEED_SETUP_PBDIAGONAL, mat_ceed, mat_coo, NULL, NULL));
92       PetscCall(PetscLogEventBegin(MATCEED_SETUP_PBDIAGONAL_CEEDOP, mat_ceed, mat_coo, NULL, NULL));
93       PetscCallCeed(ctx->ceed, CeedOperatorLinearAssemblePointBlockDiagonalSymbolic(ctx->op_mult, &num_entries, &rows_ceed, &cols_ceed));
94       PetscCall(PetscLogEventEnd(MATCEED_SETUP_PBDIAGONAL_CEEDOP, mat_ceed, mat_coo, NULL, NULL));
95       PetscCall(IntArrayCeedToPetsc(num_entries, &rows_ceed, &rows_petsc));
96       PetscCall(IntArrayCeedToPetsc(num_entries, &cols_ceed, &cols_petsc));
97       PetscCall(MatSetPreallocationCOOLocal(mat_coo, num_entries, rows_petsc, cols_petsc));
98       free(rows_petsc);
99       free(cols_petsc);
100       if (!ctx->coo_values_pbd) PetscCallCeed(ctx->ceed, CeedVectorCreate(ctx->ceed, num_entries, &ctx->coo_values_pbd));
101       PetscCall(PetscRealloc(++ctx->num_mats_assembled_pbd * sizeof(Mat), &ctx->mats_assembled_pbd));
102       ctx->mats_assembled_pbd[ctx->num_mats_assembled_pbd - 1] = mat_coo;
103       PetscCall(PetscLogEventEnd(MATCEED_SETUP_PBDIAGONAL, mat_ceed, mat_coo, NULL, NULL));
104       PetscCall(PetscLogStagePop());
105     }
106   }
107 
108   // Assemble mat_ceed
109   PetscCall(PetscLogEventBegin(MATCEED_ASSEMBLE_PBDIAGONAL, mat_ceed, mat_coo, NULL, NULL));
110   PetscCall(MatAssemblyBegin(mat_coo, MAT_FINAL_ASSEMBLY));
111   {
112     const CeedScalar *values;
113     PetscMemType      mem_type_petsc;
114     CeedMemType       mem_type = CEED_MEM_HOST;
115     PetscBool         is_spd, is_spd_known;
116 
117     PetscCall(MatGetMemTypeFromVecType(mat_coo, &mem_type_petsc));
118     mem_type = MemTypePetscToCeed(mem_type_petsc);
119     PetscCall(PetscLogEventBegin(MATCEED_ASSEMBLE_PBDIAGONAL_CEEDOP, mat_ceed, mat_coo, NULL, NULL));
120     PetscCallCeed(ctx->ceed, CeedOperatorLinearAssemblePointBlockDiagonal(ctx->op_mult, ctx->coo_values_pbd, CEED_REQUEST_IMMEDIATE));
121     PetscCall(PetscLogEventEnd(MATCEED_ASSEMBLE_PBDIAGONAL_CEEDOP, mat_ceed, mat_coo, NULL, NULL));
122     PetscCallCeed(ctx->ceed, CeedVectorGetArrayRead(ctx->coo_values_pbd, mem_type, &values));
123     PetscCall(MatSetValuesCOO(mat_coo, values, INSERT_VALUES));
124     PetscCall(MatIsSPDKnown(mat_ceed, &is_spd_known, &is_spd));
125     if (is_spd_known) PetscCall(MatSetOption(mat_coo, MAT_SPD, is_spd));
126     PetscCallCeed(ctx->ceed, CeedVectorRestoreArrayRead(ctx->coo_values_pbd, &values));
127   }
128   PetscCall(MatAssemblyEnd(mat_coo, MAT_FINAL_ASSEMBLY));
129   PetscCall(PetscLogEventEnd(MATCEED_ASSEMBLE_PBDIAGONAL, mat_ceed, mat_coo, NULL, NULL));
130   PetscFunctionReturn(PETSC_SUCCESS);
131 }
132 
133 /**
134   @brief Assemble inner `Mat` for diagonal `PC` operations
135 
136   Collective across MPI processes.
137 
138   @param[in]   mat_ceed      `MATCEED` to invert
139   @param[in]   use_ceed_pbd  Boolean flag to use libCEED PBD assembly
140   @param[out]  mat_inner     Inner `Mat` for diagonal operations
141 
142   @return An error code: 0 - success, otherwise - failure
143 **/
144 static PetscErrorCode MatCeedAssembleInnerBlockDiagonalMat(Mat mat_ceed, PetscBool use_ceed_pbd, Mat *mat_inner) {
145   MatCeedContext ctx;
146 
147   PetscFunctionBeginUser;
148   PetscCall(MatShellGetContext(mat_ceed, &ctx));
149   if (use_ceed_pbd) {
150     // Check if COO pattern set
151     if (!ctx->mat_assembled_pbd_internal) PetscCall(MatCeedCreateMatCOO(mat_ceed, &ctx->mat_assembled_pbd_internal));
152 
153     // Assemble mat_assembled_full_internal
154     PetscCall(MatCeedAssemblePointBlockDiagonalCOO(mat_ceed, ctx->mat_assembled_pbd_internal));
155     if (mat_inner) *mat_inner = ctx->mat_assembled_pbd_internal;
156   } else {
157     // Check if COO pattern set
158     if (!ctx->mat_assembled_full_internal) PetscCall(MatCeedCreateMatCOO(mat_ceed, &ctx->mat_assembled_full_internal));
159 
160     // Assemble mat_assembled_full_internal
161     PetscCall(MatCeedAssembleCOO(mat_ceed, ctx->mat_assembled_full_internal));
162     if (mat_inner) *mat_inner = ctx->mat_assembled_full_internal;
163   }
164   PetscFunctionReturn(PETSC_SUCCESS);
165 }
166 
167 /**
168   @brief Get `MATCEED` variable block diagonal for Jacobi.
169 
170   Collective across MPI processes.
171 
172   @param[in]   mat_ceed   `MATCEED` to invert
173   @param[out]  mat_vblock  The variable diagonal block matrix
174 
175   @return An error code: 0 - success, otherwise - failure
176 **/
177 static PetscErrorCode MatGetVariableBlockDiagonal_Ceed(Mat mat_ceed, Mat *mat_vblock) {
178   MatCeedContext ctx;
179 
180   PetscFunctionBeginUser;
181   PetscCall(MatShellGetContext(mat_ceed, &ctx));
182 
183   // Assemble inner mat if needed
184   PetscCall(MatCeedAssembleInnerBlockDiagonalMat(mat_ceed, ctx->is_ceed_vpbd_valid, mat_vblock));
185   PetscCall(PetscObjectReference((PetscObject)*mat_vblock));
186   PetscFunctionReturn(PETSC_SUCCESS);
187 }
188 
189 /**
190   @brief Get `MATCEED` block diagonal for Jacobi.
191 
192   Collective across MPI processes.
193 
194   @param[in]   mat_ceed   `MATCEED` to invert
195   @param[out]  mat_block  The variable diagonal block matrix
196 
197   @return An error code: 0 - success, otherwise - failure
198 **/
199 static PetscErrorCode MatGetBlockDiagonal_Ceed(Mat mat_ceed, Mat *mat_block) {
200   MatCeedContext ctx;
201 
202   PetscFunctionBeginUser;
203   PetscCall(MatShellGetContext(mat_ceed, &ctx));
204 
205   // Assemble inner mat if needed
206   PetscCall(MatCeedAssembleInnerBlockDiagonalMat(mat_ceed, ctx->is_ceed_pbd_valid, mat_block));
207   PetscCall(PetscObjectReference((PetscObject)*mat_block));
208   PetscFunctionReturn(PETSC_SUCCESS);
209 }
210 
211 /**
212   @brief Get on-process diagonal block of `MATCEED`
213 
214   This is a block per-process of the diagonal of the global matrix.
215   This is NOT the diagonal blocks associated with the block size of the matrix (i.e. `MatSetBlockSize()` has no effect on this function).
216 
217   Collective across MPI processes.
218 
219   @param[in]   mat_ceed   `MATCEED` to invert
220   @param[out]  mat_block  The diagonal block matrix
221 
222   @return An error code: 0 - success, otherwise - failure
223 **/
224 static PetscErrorCode MatGetDiagonalBlock_Ceed(Mat mat_ceed, Mat *mat_block) {
225   MatCeedContext ctx;
226 
227   PetscFunctionBeginUser;
228   PetscCall(MatShellGetContext(mat_ceed, &ctx));
229 
230   // Check if COO pattern set
231   if (!ctx->mat_assembled_full_internal) PetscCall(MatCeedCreateMatCOO(mat_ceed, &ctx->mat_assembled_full_internal));
232 
233   // Assemble mat_assembled_full_internal
234   PetscCall(MatCeedAssembleCOO(mat_ceed, ctx->mat_assembled_full_internal));
235 
236   // Get diagonal block
237   PetscCall(MatGetDiagonalBlock(ctx->mat_assembled_full_internal, mat_block));
238   PetscFunctionReturn(PETSC_SUCCESS);
239 }
240 
241 /**
242   @brief View `MATCEED`.
243 
244   Collective across MPI processes.
245 
246   @param[in]   mat_ceed  `MATCEED` to view
247   @param[in]   viewer    The visualization context
248 
249   @return An error code: 0 - success, otherwise - failure
250 **/
251 static PetscErrorCode MatView_Ceed(Mat mat_ceed, PetscViewer viewer) {
252   PetscBool         is_ascii;
253   PetscViewerFormat format;
254   PetscMPIInt       size, rank;
255   MatCeedContext    ctx;
256 
257   PetscFunctionBeginUser;
258   PetscValidHeaderSpecific(viewer, PETSC_VIEWER_CLASSID, 2);
259   PetscCall(MatShellGetContext(mat_ceed, &ctx));
260   if (!viewer) PetscCall(PetscViewerASCIIGetStdout(PetscObjectComm((PetscObject)mat_ceed), &viewer));
261 
262   PetscCall(PetscViewerGetFormat(viewer, &format));
263   PetscCallMPI(MPI_Comm_size(PetscObjectComm((PetscObject)mat_ceed), &size));
264   if (size == 1 && format == PETSC_VIEWER_LOAD_BALANCE) PetscFunctionReturn(PETSC_SUCCESS);
265 
266   PetscCallMPI(MPI_Comm_rank(PetscObjectComm((PetscObject)mat_ceed), &rank));
267   if (rank != 0) PetscFunctionReturn(PETSC_SUCCESS);
268 
269   PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &is_ascii));
270   {
271     PetscBool is_detailed     = format == PETSC_VIEWER_ASCII_INFO_DETAIL;
272     char      rank_string[16] = {'\0'};
273     FILE     *file;
274 
275     PetscCall(PetscViewerASCIIPrintf(viewer, "MatCEED:\n"));
276     PetscCall(PetscViewerASCIIPushTab(viewer));  // MatCEED
277     PetscCall(PetscViewerASCIIPrintf(viewer, "Default COO MatType: %s\n", ctx->coo_mat_type));
278     PetscCall(PetscSNPrintf(rank_string, 16, "on Rank %d", rank));
279     PetscCall(PetscViewerASCIIPrintf(viewer, "CeedOperator Apply %s:\n", is_detailed ? rank_string : "Summary"));
280     PetscCall(PetscViewerASCIIPrintf(viewer, "libCEED PB Diagonal Assembly: %s\n", ctx->is_ceed_pbd_valid ? "True" : "False"));
281     PetscCall(PetscViewerASCIIPrintf(viewer, "libCEED VPB Diagonal Assembly: %s\n", ctx->is_ceed_vpbd_valid ? "True" : "False"));
282     PetscCall(PetscViewerASCIIGetPointer(viewer, &file));
283     PetscCall(PetscViewerASCIIPushTab(viewer));  // CeedOperator
284     if (is_detailed) PetscCallCeed(ctx->ceed, CeedOperatorView(ctx->op_mult, file));
285     else PetscCallCeed(ctx->ceed, CeedOperatorViewTerse(ctx->op_mult, file));
286     PetscCall(PetscViewerASCIIPopTab(viewer));  // CeedOperator
287     if (ctx->op_mult_transpose) {
288       PetscCall(PetscViewerASCIIPrintf(viewer, "CeedOperator ApplyTranspose %s:\n", is_detailed ? rank_string : "Summary"));
289       PetscCall(PetscViewerASCIIPushTab(viewer));  // CeedOperator
290       if (is_detailed) PetscCallCeed(ctx->ceed, CeedOperatorView(ctx->op_mult_transpose, file));
291       else PetscCallCeed(ctx->ceed, CeedOperatorViewTerse(ctx->op_mult_transpose, file));
292       PetscCall(PetscViewerASCIIPopTab(viewer));  // CeedOperator
293     }
294     PetscCall(PetscViewerASCIIPopTab(viewer));  // MatCEED
295   }
296   PetscFunctionReturn(PETSC_SUCCESS);
297 }
298 
299 // -----------------------------------------------------------------------------
300 // MatCeed
301 // -----------------------------------------------------------------------------
302 
303 /**
304   @brief Create PETSc `Mat` from libCEED operators.
305 
306   Collective across MPI processes.
307 
308   @param[in]   dm_x                      Input `DM`
309   @param[in]   dm_y                      Output `DM`
310   @param[in]   op_mult                   `CeedOperator` for forward evaluation
311   @param[in]   op_mult_transpose         `CeedOperator` for transpose evaluation
312   @param[out]  mat                        New MatCeed
313 
314   @return An error code: 0 - success, otherwise - failure
315 **/
316 PetscErrorCode MatCreateCeed(DM dm_x, DM dm_y, CeedOperator op_mult, CeedOperator op_mult_transpose, Mat *mat) {
317   PetscInt       X_l_size, X_g_size, Y_l_size, Y_g_size;
318   VecType        vec_type;
319   MatCeedContext ctx;
320 
321   PetscFunctionBeginUser;
322   PetscCall(MatCeedRegisterLogEvents());
323 
324   // Collect context data
325   PetscCall(DMGetVecType(dm_x, &vec_type));
326   {
327     Vec X;
328 
329     PetscCall(DMGetGlobalVector(dm_x, &X));
330     PetscCall(VecGetSize(X, &X_g_size));
331     PetscCall(VecGetLocalSize(X, &X_l_size));
332     PetscCall(DMRestoreGlobalVector(dm_x, &X));
333   }
334   if (dm_y) {
335     Vec Y;
336 
337     PetscCall(DMGetGlobalVector(dm_y, &Y));
338     PetscCall(VecGetSize(Y, &Y_g_size));
339     PetscCall(VecGetLocalSize(Y, &Y_l_size));
340     PetscCall(DMRestoreGlobalVector(dm_y, &Y));
341   } else {
342     dm_y     = dm_x;
343     Y_g_size = X_g_size;
344     Y_l_size = X_l_size;
345   }
346 
347   // Create context
348   {
349     Vec X_loc, Y_loc_transpose = NULL;
350 
351     PetscCall(DMCreateLocalVector(dm_x, &X_loc));
352     PetscCall(VecZeroEntries(X_loc));
353     if (op_mult_transpose) {
354       PetscCall(DMCreateLocalVector(dm_y, &Y_loc_transpose));
355       PetscCall(VecZeroEntries(Y_loc_transpose));
356     }
357     PetscCall(MatCeedContextCreate(dm_x, dm_y, X_loc, Y_loc_transpose, op_mult, op_mult_transpose, MATCEED_MULT, MATCEED_MULT_TRANSPOSE,
358                                    MATCEED_MULT_CEEDOP, MATCEED_MULT_TRANSPOSE_CEEDOP, &ctx));
359     PetscCall(VecDestroy(&X_loc));
360     PetscCall(VecDestroy(&Y_loc_transpose));
361   }
362 
363   // Create mat
364   PetscCall(MatCreateShell(PetscObjectComm((PetscObject)dm_x), Y_l_size, X_l_size, Y_g_size, X_g_size, ctx, mat));
365   PetscCall(PetscObjectChangeTypeName((PetscObject)*mat, MATCEED));
366   // -- Set block and variable block sizes
367   if (dm_x == dm_y) {
368     MatType dm_mat_type, dm_mat_type_copy;
369     Mat     temp_mat;
370 
371     PetscCall(DMGetMatType(dm_x, &dm_mat_type));
372     PetscCall(PetscStrallocpy(dm_mat_type, (char **)&dm_mat_type_copy));
373     PetscCall(DMSetMatType(dm_x, MATAIJ));
374     PetscCall(DMCreateMatrix(dm_x, &temp_mat));
375     PetscCall(DMSetMatType(dm_x, dm_mat_type_copy));
376     PetscCall(PetscFree(dm_mat_type_copy));
377 
378     {
379       PetscInt        block_size, num_blocks, max_vblock_size = PETSC_INT_MAX;
380       const PetscInt *vblock_sizes;
381 
382       // -- Get block sizes
383       PetscCall(MatGetBlockSize(temp_mat, &block_size));
384       PetscCall(MatGetVariableBlockSizes(temp_mat, &num_blocks, &vblock_sizes));
385       {
386         PetscInt local_min_max[2] = {0}, global_min_max[2] = {0, PETSC_INT_MAX};
387 
388         for (PetscInt i = 0; i < num_blocks; i++) local_min_max[1] = PetscMax(local_min_max[1], vblock_sizes[i]);
389         PetscCall(PetscGlobalMinMaxInt(PetscObjectComm((PetscObject)dm_x), local_min_max, global_min_max));
390         max_vblock_size = global_min_max[1];
391       }
392 
393       // -- Copy block sizes
394       if (block_size > 1) PetscCall(MatSetBlockSize(*mat, block_size));
395       if (num_blocks) PetscCall(MatSetVariableBlockSizes(*mat, num_blocks, (PetscInt *)vblock_sizes));
396 
397       // -- Check libCEED compatibility
398       {
399         bool is_composite;
400 
401         ctx->is_ceed_pbd_valid  = PETSC_TRUE;
402         ctx->is_ceed_vpbd_valid = PETSC_TRUE;
403         PetscCallCeed(ctx->ceed, CeedOperatorIsComposite(op_mult, &is_composite));
404         if (is_composite) {
405           CeedInt       num_sub_operators;
406           CeedOperator *sub_operators;
407 
408           PetscCallCeed(ctx->ceed, CeedCompositeOperatorGetNumSub(op_mult, &num_sub_operators));
409           PetscCallCeed(ctx->ceed, CeedCompositeOperatorGetSubList(op_mult, &sub_operators));
410           for (CeedInt i = 0; i < num_sub_operators; i++) {
411             CeedInt                  num_bases, num_comp;
412             CeedBasis               *active_bases;
413             CeedOperatorAssemblyData assembly_data;
414 
415             PetscCallCeed(ctx->ceed, CeedOperatorGetOperatorAssemblyData(sub_operators[i], &assembly_data));
416             PetscCallCeed(ctx->ceed, CeedOperatorAssemblyDataGetBases(assembly_data, &num_bases, &active_bases, NULL, NULL, NULL, NULL));
417             PetscCallCeed(ctx->ceed, CeedBasisGetNumComponents(active_bases[0], &num_comp));
418             if (num_bases > 1) {
419               ctx->is_ceed_pbd_valid  = PETSC_FALSE;
420               ctx->is_ceed_vpbd_valid = PETSC_FALSE;
421             }
422             if (num_comp != block_size) ctx->is_ceed_pbd_valid = PETSC_FALSE;
423             if (num_comp < max_vblock_size) ctx->is_ceed_vpbd_valid = PETSC_FALSE;
424           }
425         } else {
426           // LCOV_EXCL_START
427           CeedInt                  num_bases, num_comp;
428           CeedBasis               *active_bases;
429           CeedOperatorAssemblyData assembly_data;
430 
431           PetscCallCeed(ctx->ceed, CeedOperatorGetOperatorAssemblyData(op_mult, &assembly_data));
432           PetscCallCeed(ctx->ceed, CeedOperatorAssemblyDataGetBases(assembly_data, &num_bases, &active_bases, NULL, NULL, NULL, NULL));
433           PetscCallCeed(ctx->ceed, CeedBasisGetNumComponents(active_bases[0], &num_comp));
434           if (num_bases > 1) {
435             ctx->is_ceed_pbd_valid  = PETSC_FALSE;
436             ctx->is_ceed_vpbd_valid = PETSC_FALSE;
437           }
438           if (num_comp != block_size) ctx->is_ceed_pbd_valid = PETSC_FALSE;
439           if (num_comp < max_vblock_size) ctx->is_ceed_vpbd_valid = PETSC_FALSE;
440           // LCOV_EXCL_STOP
441         }
442         {
443           PetscInt local_is_valid[2], global_is_valid[2];
444 
445           local_is_valid[0] = local_is_valid[1] = ctx->is_ceed_pbd_valid;
446           PetscCall(PetscGlobalMinMaxInt(PetscObjectComm((PetscObject)dm_x), local_is_valid, global_is_valid));
447           ctx->is_ceed_pbd_valid = global_is_valid[0];
448           local_is_valid[0] = local_is_valid[1] = ctx->is_ceed_vpbd_valid;
449           PetscCall(PetscGlobalMinMaxInt(PetscObjectComm((PetscObject)dm_x), local_is_valid, global_is_valid));
450           ctx->is_ceed_vpbd_valid = global_is_valid[0];
451         }
452       }
453     }
454     PetscCall(MatDestroy(&temp_mat));
455   }
456   // -- Set internal mat type
457   {
458     VecType vec_type;
459     MatType coo_mat_type;
460 
461     PetscCall(VecGetType(ctx->X_loc, &vec_type));
462     PetscCall(DefaultMatTypeFromVecType(vec_type, &coo_mat_type));
463     PetscCall(PetscStrallocpy(coo_mat_type, &ctx->coo_mat_type));
464   }
465   // -- Set mat operations
466   PetscCall(MatShellSetContextDestroy(*mat, (PetscCtxDestroyFn *)MatCeedContextDestroy));
467   PetscCall(MatShellSetOperation(*mat, MATOP_VIEW, (void (*)(void))MatView_Ceed));
468   PetscCall(MatShellSetOperation(*mat, MATOP_MULT, (void (*)(void))MatMult_Ceed));
469   if (op_mult_transpose) PetscCall(MatShellSetOperation(*mat, MATOP_MULT_TRANSPOSE, (void (*)(void))MatMultTranspose_Ceed));
470   PetscCall(MatShellSetOperation(*mat, MATOP_GET_DIAGONAL, (void (*)(void))MatGetDiagonal_Ceed));
471   PetscCall(MatShellSetOperation(*mat, MATOP_GET_DIAGONAL_BLOCK, (void (*)(void))MatGetDiagonalBlock_Ceed));
472   PetscCall(MatShellSetOperation(*mat, MATOP_GET_BLOCK_DIAGONAL, (void (*)(void))MatGetBlockDiagonal_Ceed));
473   PetscCall(MatShellSetOperation(*mat, MATOP_GET_VBLOCK_DIAGONAL, (void (*)(void))MatGetVariableBlockDiagonal_Ceed));
474   PetscCall(MatShellSetVecType(*mat, vec_type));
475   PetscFunctionReturn(PETSC_SUCCESS);
476 }
477 
478 /**
479   @brief Copy `MATCEED` into a compatible `Mat` with type `MatShell` or `MATCEED`.
480 
481   Collective across MPI processes.
482 
483   @param[in]   mat_ceed   `MATCEED` to copy from
484   @param[out]  mat_other  `MatShell` or `MATCEED` to copy into
485 
486   @return An error code: 0 - success, otherwise - failure
487 **/
488 PetscErrorCode MatCeedCopy(Mat mat_ceed, Mat mat_other) {
489   PetscFunctionBeginUser;
490   PetscCall(MatCeedRegisterLogEvents());
491 
492   // Check type compatibility
493   {
494     PetscBool is_matceed = PETSC_FALSE, is_matshell = PETSC_FALSE;
495     MatType   mat_type_ceed, mat_type_other;
496 
497     PetscCall(MatGetType(mat_ceed, &mat_type_ceed));
498     PetscCall(PetscStrcmp(mat_type_ceed, MATCEED, &is_matceed));
499     PetscCheck(is_matceed, PETSC_COMM_SELF, PETSC_ERR_LIB, "mat_ceed must have type " MATCEED);
500     PetscCall(MatGetType(mat_other, &mat_type_other));
501     PetscCall(PetscStrcmp(mat_type_other, MATCEED, &is_matceed));
502     PetscCall(PetscStrcmp(mat_type_other, MATSHELL, &is_matceed));
503     PetscCheck(is_matceed || is_matshell, PETSC_COMM_SELF, PETSC_ERR_LIB, "mat_other must have type " MATCEED " or " MATSHELL);
504   }
505 
506   // Check dimension compatibility
507   {
508     PetscInt X_l_ceed_size, X_g_ceed_size, Y_l_ceed_size, Y_g_ceed_size, X_l_other_size, X_g_other_size, Y_l_other_size, Y_g_other_size;
509 
510     PetscCall(MatGetSize(mat_ceed, &Y_g_ceed_size, &X_g_ceed_size));
511     PetscCall(MatGetLocalSize(mat_ceed, &Y_l_ceed_size, &X_l_ceed_size));
512     PetscCall(MatGetSize(mat_ceed, &Y_g_other_size, &X_g_other_size));
513     PetscCall(MatGetLocalSize(mat_ceed, &Y_l_other_size, &X_l_other_size));
514     PetscCheck((Y_g_ceed_size == Y_g_other_size) && (X_g_ceed_size == X_g_other_size) && (Y_l_ceed_size == Y_l_other_size) &&
515                    (X_l_ceed_size == X_l_other_size),
516                PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
517                "mat_ceed and mat_other must have compatible sizes; found mat_ceed (Global: %" PetscInt_FMT ", %" PetscInt_FMT
518                "; Local: %" PetscInt_FMT ", %" PetscInt_FMT ") mat_other (Global: %" PetscInt_FMT ", %" PetscInt_FMT "; Local: %" PetscInt_FMT
519                ", %" PetscInt_FMT ")",
520                Y_g_ceed_size, X_g_ceed_size, Y_l_ceed_size, X_l_ceed_size, Y_g_other_size, X_g_other_size, Y_l_other_size, X_l_other_size);
521   }
522 
523   // Convert
524   {
525     VecType        vec_type;
526     MatCeedContext ctx;
527 
528     PetscCall(PetscObjectChangeTypeName((PetscObject)mat_other, MATCEED));
529     PetscCall(MatShellGetContext(mat_ceed, &ctx));
530     PetscCall(MatCeedContextReference(ctx));
531     PetscCall(MatShellSetContext(mat_other, ctx));
532     PetscCall(MatShellSetContextDestroy(mat_other, (PetscCtxDestroyFn *)MatCeedContextDestroy));
533     PetscCall(MatShellSetOperation(mat_other, MATOP_VIEW, (void (*)(void))MatView_Ceed));
534     PetscCall(MatShellSetOperation(mat_other, MATOP_MULT, (void (*)(void))MatMult_Ceed));
535     if (ctx->op_mult_transpose) PetscCall(MatShellSetOperation(mat_other, MATOP_MULT_TRANSPOSE, (void (*)(void))MatMultTranspose_Ceed));
536     PetscCall(MatShellSetOperation(mat_other, MATOP_GET_DIAGONAL, (void (*)(void))MatGetDiagonal_Ceed));
537     PetscCall(MatShellSetOperation(mat_other, MATOP_GET_DIAGONAL_BLOCK, (void (*)(void))MatGetDiagonalBlock_Ceed));
538     PetscCall(MatShellSetOperation(mat_other, MATOP_GET_BLOCK_DIAGONAL, (void (*)(void))MatGetBlockDiagonal_Ceed));
539     PetscCall(MatShellSetOperation(mat_other, MATOP_GET_VBLOCK_DIAGONAL, (void (*)(void))MatGetVariableBlockDiagonal_Ceed));
540     {
541       PetscInt block_size;
542 
543       PetscCall(MatGetBlockSize(mat_ceed, &block_size));
544       if (block_size > 1) PetscCall(MatSetBlockSize(mat_other, block_size));
545     }
546     {
547       PetscInt        num_blocks;
548       const PetscInt *block_sizes;
549 
550       PetscCall(MatGetVariableBlockSizes(mat_ceed, &num_blocks, &block_sizes));
551       if (num_blocks) PetscCall(MatSetVariableBlockSizes(mat_other, num_blocks, (PetscInt *)block_sizes));
552     }
553     PetscCall(DMGetVecType(ctx->dm_x, &vec_type));
554     PetscCall(MatShellSetVecType(mat_other, vec_type));
555   }
556   PetscFunctionReturn(PETSC_SUCCESS);
557 }
558 
559 /**
560   @brief Mark `CeedQFunction` data as updated and the `CeedQFunction` as requiring re-assembly for a `MatCEED`.
561 
562   Collective across MPI processes.
563 
564   @param[in]   mat_ceed       `MATCEED`
565   @param[out]  update_needed  Boolean flag indicating `CeedQFunction` update needed
566 
567   @return An error code: 0 - success, otherwise - failure
568 **/
569 PetscErrorCode MatCeedSetAssemblyDataUpdateNeeded(Mat mat_ceed, PetscBool update_needed) {
570   MatCeedContext ctx;
571 
572   PetscFunctionBeginUser;
573   PetscCall(MatShellGetContext(mat_ceed, &ctx));
574   PetscCallCeed(ctx->ceed, CeedOperatorSetQFunctionAssemblyDataUpdateNeeded(ctx->op_mult, update_needed));
575   if (ctx->op_mult_transpose) {
576     PetscCallCeed(ctx->ceed, CeedOperatorSetQFunctionAssemblyDataUpdateNeeded(ctx->op_mult_transpose, update_needed));
577   }
578   if (update_needed) {
579     PetscCall(MatAssemblyBegin(mat_ceed, MAT_FINAL_ASSEMBLY));
580     PetscCall(MatAssemblyEnd(mat_ceed, MAT_FINAL_ASSEMBLY));
581   }
582   PetscFunctionReturn(PETSC_SUCCESS);
583 }
584 
585 /**
586   @brief Setup a `Mat` with the same COO pattern as a `MatCEED`.
587 
588   Collective across MPI processes.
589 
590   @param[in]   mat_ceed  `MATCEED`
591   @param[out]  mat_coo   Sparse `Mat` with same COO pattern
592 
593   @return An error code: 0 - success, otherwise - failure
594 **/
595 PetscErrorCode MatCeedCreateMatCOO(Mat mat_ceed, Mat *mat_coo) {
596   MatCeedContext ctx;
597 
598   PetscFunctionBeginUser;
599   PetscCall(MatShellGetContext(mat_ceed, &ctx));
600 
601   PetscCheck(ctx->dm_x == ctx->dm_y, PetscObjectComm((PetscObject)mat_ceed), PETSC_ERR_SUP, "COO assembly only supported for MATCEED on a single DM");
602 
603   // Check cl mat type
604   {
605     PetscBool is_coo_mat_type_cl = PETSC_FALSE;
606     char      coo_mat_type_cl[PETSC_MAX_OPTION_NAME];
607 
608     // Check for specific CL coo mat type for this Mat
609     {
610       const char *mat_ceed_prefix = NULL;
611 
612       PetscCall(MatGetOptionsPrefix(mat_ceed, &mat_ceed_prefix));
613       PetscOptionsBegin(PetscObjectComm((PetscObject)mat_ceed), mat_ceed_prefix, "", NULL);
614       PetscCall(PetscOptionsFList("-ceed_coo_mat_type", "Default MATCEED COO assembly MatType", NULL, MatList, coo_mat_type_cl, coo_mat_type_cl,
615                                   sizeof(coo_mat_type_cl), &is_coo_mat_type_cl));
616       PetscOptionsEnd();
617       if (is_coo_mat_type_cl) {
618         PetscCall(PetscFree(ctx->coo_mat_type));
619         PetscCall(PetscStrallocpy(coo_mat_type_cl, &ctx->coo_mat_type));
620       }
621     }
622   }
623 
624   // Create sparse matrix
625   {
626     MatType dm_mat_type, dm_mat_type_copy;
627 
628     PetscCall(DMGetMatType(ctx->dm_x, &dm_mat_type));
629     PetscCall(PetscStrallocpy(dm_mat_type, (char **)&dm_mat_type_copy));
630     PetscCall(DMSetMatType(ctx->dm_x, ctx->coo_mat_type));
631     PetscCall(DMCreateMatrix(ctx->dm_x, mat_coo));
632     PetscCall(DMSetMatType(ctx->dm_x, dm_mat_type_copy));
633     PetscCall(PetscFree(dm_mat_type_copy));
634   }
635   PetscFunctionReturn(PETSC_SUCCESS);
636 }
637 
638 /**
639   @brief Setup the COO preallocation `MATCEED` into a `MATAIJ` or similar.
640          The caller is responsible for assuring the global and local sizes are compatible, otherwise this function will fail.
641 
642   Collective across MPI processes.
643 
644   @param[in]      mat_ceed  `MATCEED` to assemble
645   @param[in,out]  mat_coo   `MATAIJ` or similar to assemble into
646 
647   @return An error code: 0 - success, otherwise - failure
648 **/
649 PetscErrorCode MatCeedSetPreallocationCOO(Mat mat_ceed, Mat mat_coo) {
650   MatCeedContext ctx;
651 
652   PetscFunctionBeginUser;
653   PetscCall(MatShellGetContext(mat_ceed, &ctx));
654 
655   {
656     PetscInt     *rows_petsc = NULL, *cols_petsc = NULL;
657     CeedInt      *rows_ceed, *cols_ceed;
658     PetscCount    num_entries;
659     PetscLogStage stage_amg_setup;
660 
661     // -- Assemble sparsity pattern if mat hasn't been assembled before
662     PetscCall(PetscLogStageGetId("MatCEED Asm Setup", &stage_amg_setup));
663     if (stage_amg_setup == -1) {
664       PetscCall(PetscLogStageRegister("MatCEED Asm Setup", &stage_amg_setup));
665     }
666     PetscCall(PetscLogStagePush(stage_amg_setup));
667     PetscCall(PetscLogEventBegin(MATCEED_SETUP_FULL, mat_ceed, mat_coo, NULL, NULL));
668     PetscCall(PetscLogEventBegin(MATCEED_SETUP_FULL_CEEDOP, mat_ceed, mat_coo, NULL, NULL));
669     PetscCallCeed(ctx->ceed, CeedOperatorLinearAssembleSymbolic(ctx->op_mult, &num_entries, &rows_ceed, &cols_ceed));
670     PetscCall(PetscLogEventEnd(MATCEED_SETUP_FULL_CEEDOP, mat_ceed, mat_coo, NULL, NULL));
671     PetscCall(IntArrayCeedToPetsc(num_entries, &rows_ceed, &rows_petsc));
672     PetscCall(IntArrayCeedToPetsc(num_entries, &cols_ceed, &cols_petsc));
673     PetscCall(MatSetPreallocationCOOLocal(mat_coo, num_entries, rows_petsc, cols_petsc));
674     free(rows_petsc);
675     free(cols_petsc);
676     if (!ctx->coo_values_full) PetscCallCeed(ctx->ceed, CeedVectorCreate(ctx->ceed, num_entries, &ctx->coo_values_full));
677     PetscCall(PetscRealloc(++ctx->num_mats_assembled_full * sizeof(Mat), &ctx->mats_assembled_full));
678     ctx->mats_assembled_full[ctx->num_mats_assembled_full - 1] = mat_coo;
679     PetscCall(PetscLogEventEnd(MATCEED_SETUP_FULL, mat_ceed, mat_coo, NULL, NULL));
680     PetscCall(PetscLogStagePop());
681   }
682   PetscFunctionReturn(PETSC_SUCCESS);
683 }
684 
685 /**
686   @brief Assemble a `MATCEED` into a `MATAIJ` or similar.
687          The `mat_coo` preallocation is set to match the sparsity pattern of `mat_ceed`.
688          The caller is responsible for assuring the global and local sizes are compatible, otherwise this function will fail.
689 
690   Collective across MPI processes.
691 
692   @param[in]      mat_ceed  `MATCEED` to assemble
693   @param[in,out]  mat_coo   `MATAIJ` or similar to assemble into
694 
695   @return An error code: 0 - success, otherwise - failure
696 **/
697 PetscErrorCode MatCeedAssembleCOO(Mat mat_ceed, Mat mat_coo) {
698   MatCeedContext ctx;
699 
700   PetscFunctionBeginUser;
701   PetscCall(MatShellGetContext(mat_ceed, &ctx));
702 
703   // Set COO pattern if needed
704   {
705     CeedInt index = -1;
706 
707     for (PetscInt i = 0; i < ctx->num_mats_assembled_full; i++) {
708       if (ctx->mats_assembled_full[i] == mat_coo) index = i;
709     }
710     if (index == -1) PetscCall(MatCeedSetPreallocationCOO(mat_ceed, mat_coo));
711   }
712 
713   // Assemble mat_ceed
714   PetscCall(PetscLogEventBegin(MATCEED_ASSEMBLE_FULL, mat_ceed, mat_coo, NULL, NULL));
715   PetscCall(MatAssemblyBegin(mat_coo, MAT_FINAL_ASSEMBLY));
716   {
717     const CeedScalar *values;
718     CeedMemType       mem_type = CEED_MEM_HOST;
719     PetscBool         is_spd, is_spd_known;
720     PetscMemType      mem_type_petsc;
721 
722     PetscCall(MatGetMemTypeFromVecType(mat_coo, &mem_type_petsc));
723     mem_type = MemTypePetscToCeed(mem_type_petsc);
724     PetscCall(PetscLogEventBegin(MATCEED_ASSEMBLE_FULL_CEEDOP, mat_ceed, mat_coo, NULL, NULL));
725     PetscCallCeed(ctx->ceed, CeedOperatorLinearAssemble(ctx->op_mult, ctx->coo_values_full));
726     PetscCall(PetscLogEventEnd(MATCEED_ASSEMBLE_FULL_CEEDOP, mat_ceed, mat_coo, NULL, NULL));
727     PetscCallCeed(ctx->ceed, CeedVectorGetArrayRead(ctx->coo_values_full, mem_type, &values));
728     PetscCall(MatSetValuesCOO(mat_coo, values, INSERT_VALUES));
729     PetscCall(MatIsSPDKnown(mat_ceed, &is_spd_known, &is_spd));
730     if (is_spd_known) PetscCall(MatSetOption(mat_coo, MAT_SPD, is_spd));
731     PetscCallCeed(ctx->ceed, CeedVectorRestoreArrayRead(ctx->coo_values_full, &values));
732   }
733   PetscCall(MatAssemblyEnd(mat_coo, MAT_FINAL_ASSEMBLY));
734   PetscCall(PetscLogEventEnd(MATCEED_ASSEMBLE_FULL, mat_ceed, mat_coo, NULL, NULL));
735   PetscFunctionReturn(PETSC_SUCCESS);
736 }
737 
738 /**
739   @brief Set the current value of a context field for a `MatCEED`.
740 
741   Not collective across MPI processes.
742 
743   @param[in,out]  mat    `MatCEED`
744   @param[in]      name   Name of the context field
745   @param[in]      value  New context field value
746 
747   @return An error code: 0 - success, otherwise - failure
748 **/
749 PetscErrorCode MatCeedSetContextDouble(Mat mat, const char *name, double value) {
750   PetscBool      was_updated = PETSC_FALSE;
751   MatCeedContext ctx;
752 
753   PetscFunctionBeginUser;
754   PetscCall(MatShellGetContext(mat, &ctx));
755   {
756     CeedContextFieldLabel label = NULL;
757 
758     PetscCallCeed(ctx->ceed, CeedOperatorGetContextFieldLabel(ctx->op_mult, name, &label));
759     if (label) {
760       double set_value = 2 * value + 1.0;
761 
762       PetscCall(MatCeedGetContextDouble(mat, name, &set_value));
763       if (set_value != value) {
764         PetscCallCeed(ctx->ceed, CeedOperatorSetContextDouble(ctx->op_mult, label, &value));
765         was_updated = PETSC_TRUE;
766       }
767     }
768     if (ctx->op_mult_transpose) {
769       label = NULL;
770       PetscCallCeed(ctx->ceed, CeedOperatorGetContextFieldLabel(ctx->op_mult_transpose, name, &label));
771       if (label) {
772         double set_value = 2 * value + 1.0;
773 
774         PetscCall(MatCeedGetContextDouble(mat, name, &set_value));
775         if (set_value != value) {
776           PetscCallCeed(ctx->ceed, CeedOperatorSetContextDouble(ctx->op_mult_transpose, label, &value));
777           was_updated = PETSC_TRUE;
778         }
779       }
780     }
781   }
782   if (was_updated) {
783     PetscCall(MatAssemblyBegin(mat, MAT_FINAL_ASSEMBLY));
784     PetscCall(MatAssemblyEnd(mat, MAT_FINAL_ASSEMBLY));
785   }
786   PetscFunctionReturn(PETSC_SUCCESS);
787 }
788 
789 /**
790   @brief Get the current value of a context field for a `MatCEED`.
791 
792   Not collective across MPI processes.
793 
794   @param[in]   mat    `MatCEED`
795   @param[in]   name   Name of the context field
796   @param[out]  value  Current context field value
797 
798   @return An error code: 0 - success, otherwise - failure
799 **/
800 PetscErrorCode MatCeedGetContextDouble(Mat mat, const char *name, double *value) {
801   MatCeedContext ctx;
802 
803   PetscFunctionBeginUser;
804   PetscCall(MatShellGetContext(mat, &ctx));
805   {
806     CeedContextFieldLabel label = NULL;
807     CeedOperator          op    = ctx->op_mult;
808 
809     PetscCallCeed(ctx->ceed, CeedOperatorGetContextFieldLabel(op, name, &label));
810     if (!label && ctx->op_mult_transpose) {
811       op = ctx->op_mult_transpose;
812       PetscCallCeed(ctx->ceed, CeedOperatorGetContextFieldLabel(op, name, &label));
813     }
814     if (label) {
815       PetscSizeT    num_values;
816       const double *values_ceed;
817 
818       PetscCallCeed(ctx->ceed, CeedOperatorGetContextDoubleRead(op, label, &num_values, &values_ceed));
819       *value = values_ceed[0];
820       PetscCallCeed(ctx->ceed, CeedOperatorRestoreContextDoubleRead(op, label, &values_ceed));
821     }
822   }
823   PetscFunctionReturn(PETSC_SUCCESS);
824 }
825 
826 /**
827   @brief Set the current `PetscReal` value of a context field for a `MatCEED`.
828 
829   Not collective across MPI processes.
830 
831   @param[in,out]  mat    `MatCEED`
832   @param[in]      name   Name of the context field
833   @param[in]      value  New context field value
834 
835   @return An error code: 0 - success, otherwise - failure
836 **/
837 PetscErrorCode MatCeedSetContextReal(Mat mat, const char *name, PetscReal value) {
838   double value_double = value;
839 
840   PetscFunctionBeginUser;
841   PetscCall(MatCeedSetContextDouble(mat, name, value_double));
842   PetscFunctionReturn(PETSC_SUCCESS);
843 }
844 
845 /**
846   @brief Get the current `PetscReal` value of a context field for a `MatCEED`.
847 
848   Not collective across MPI processes.
849 
850   @param[in]   mat    `MatCEED`
851   @param[in]   name   Name of the context field
852   @param[out]  value  Current context field value
853 
854   @return An error code: 0 - success, otherwise - failure
855 **/
856 PetscErrorCode MatCeedGetContextReal(Mat mat, const char *name, PetscReal *value) {
857   double value_double = 0.0;
858 
859   PetscFunctionBeginUser;
860   PetscCall(MatCeedGetContextDouble(mat, name, &value_double));
861   *value = value_double;
862   PetscFunctionReturn(PETSC_SUCCESS);
863 }
864 
865 /**
866   @brief Set the current time for a `MatCEED`.
867 
868   Not collective across MPI processes.
869 
870   @param[in,out]  mat   `MatCEED`
871   @param[in]      time  Current time
872 
873   @return An error code: 0 - success, otherwise - failure
874 **/
875 PetscErrorCode MatCeedSetTime(Mat mat, PetscReal time) {
876   PetscFunctionBeginUser;
877   {
878     double time_ceed = time;
879 
880     PetscCall(MatCeedSetContextDouble(mat, "time", time_ceed));
881   }
882   PetscFunctionReturn(PETSC_SUCCESS);
883 }
884 
885 /**
886   @brief Get the current time for a `MatCEED`.
887 
888   Not collective across MPI processes.
889 
890   @param[in]   mat   `MatCEED`
891   @param[out]  time  Current time, or -1.0 if the boundary evaluator has no time field
892 
893   @return An error code: 0 - success, otherwise - failure
894 **/
895 PetscErrorCode MatCeedGetTime(Mat mat, PetscReal *time) {
896   PetscFunctionBeginUser;
897   *time = -1.0;
898   {
899     double time_ceed = -1.0;
900 
901     PetscCall(MatCeedGetContextDouble(mat, "time", &time_ceed));
902     *time = time_ceed;
903   }
904   PetscFunctionReturn(PETSC_SUCCESS);
905 }
906 
907 /**
908   @brief Set the current time step for a `MatCEED`.
909 
910   Not collective across MPI processes.
911 
912   @param[in,out]  mat  `MatCEED`
913   @param[in]      dt   Current time step
914 
915   @return An error code: 0 - success, otherwise - failure
916 **/
917 PetscErrorCode MatCeedSetDt(Mat mat, PetscReal dt) {
918   PetscFunctionBeginUser;
919   {
920     double dt_ceed = dt;
921 
922     PetscCall(MatCeedSetContextDouble(mat, "dt", dt_ceed));
923   }
924   PetscFunctionReturn(PETSC_SUCCESS);
925 }
926 
927 /**
928   @brief Set the Jacobian shifts for a `MatCEED`.
929 
930   Not collective across MPI processes.
931 
932   @param[in,out]  mat      `MatCEED`
933   @param[in]      shift_v  Velocity shift
934   @param[in]      shift_a  Acceleration shift
935 
936   @return An error code: 0 - success, otherwise - failure
937 **/
938 PetscErrorCode MatCeedSetShifts(Mat mat, PetscReal shift_v, PetscReal shift_a) {
939   PetscFunctionBeginUser;
940   {
941     double shift_v_ceed = shift_v;
942 
943     PetscCall(MatCeedSetContextDouble(mat, "shift v", shift_v_ceed));
944   }
945   if (shift_a) {
946     double shift_a_ceed = shift_a;
947 
948     PetscCall(MatCeedSetContextDouble(mat, "shift a", shift_a_ceed));
949   }
950   PetscFunctionReturn(PETSC_SUCCESS);
951 }
952 
953 /**
954   @brief Set user context for a `MATCEED`.
955 
956   Collective across MPI processes.
957 
958   @param[in,out]  mat  `MATCEED`
959   @param[in]      f    The context destroy function, or NULL
960   @param[in]      ctx  User context, or NULL to unset
961 
962   @return An error code: 0 - success, otherwise - failure
963 **/
964 PetscErrorCode MatCeedSetContext(Mat mat, PetscCtxDestroyFn f, void *ctx) {
965   PetscContainer user_ctx = NULL;
966 
967   PetscFunctionBeginUser;
968   if (ctx) {
969     PetscCall(PetscContainerCreate(PetscObjectComm((PetscObject)mat), &user_ctx));
970     PetscCall(PetscContainerSetPointer(user_ctx, ctx));
971     PetscCall(PetscContainerSetCtxDestroy(user_ctx, f));
972   }
973   PetscCall(PetscObjectCompose((PetscObject)mat, "MatCeed user context", (PetscObject)user_ctx));
974   PetscCall(PetscContainerDestroy(&user_ctx));
975   PetscFunctionReturn(PETSC_SUCCESS);
976 }
977 
978 /**
979   @brief Retrieve the user context for a `MATCEED`.
980 
981   Collective across MPI processes.
982 
983   @param[in,out]  mat  `MATCEED`
984   @param[in]      ctx  User context
985 
986   @return An error code: 0 - success, otherwise - failure
987 **/
988 PetscErrorCode MatCeedGetContext(Mat mat, void *ctx) {
989   PetscContainer user_ctx;
990 
991   PetscFunctionBeginUser;
992   PetscCall(PetscObjectQuery((PetscObject)mat, "MatCeed user context", (PetscObject *)&user_ctx));
993   if (user_ctx) PetscCall(PetscContainerGetPointer(user_ctx, (void **)ctx));
994   else *(void **)ctx = NULL;
995   PetscFunctionReturn(PETSC_SUCCESS);
996 }
997 /**
998   @brief Set a user defined matrix operation for a `MATCEED` matrix.
999 
1000   Within each user-defined routine, the user should call `MatCeedGetContext()` to obtain the user-defined context that was set by
1001 `MatCeedSetContext()`.
1002 
1003   Collective across MPI processes.
1004 
1005   @param[in,out]  mat  `MATCEED`
1006   @param[in]      op   Name of the `MatOperation`
1007   @param[in]      g    Function that provides the operation
1008 
1009   @return An error code: 0 - success, otherwise - failure
1010 **/
1011 PetscErrorCode MatCeedSetOperation(Mat mat, MatOperation op, void (*g)(void)) {
1012   PetscFunctionBeginUser;
1013   PetscCall(MatShellSetOperation(mat, op, g));
1014   PetscFunctionReturn(PETSC_SUCCESS);
1015 }
1016 
1017 /**
1018   @brief Sets the default COO matrix type as a string from the `MATCEED`.
1019 
1020   Collective across MPI processes.
1021 
1022   @param[in,out]  mat   `MATCEED`
1023   @param[in]      type  COO `MatType` to set
1024 
1025   @return An error code: 0 - success, otherwise - failure
1026 **/
1027 PetscErrorCode MatCeedSetCOOMatType(Mat mat, MatType type) {
1028   MatCeedContext ctx;
1029 
1030   PetscFunctionBeginUser;
1031   PetscCall(MatShellGetContext(mat, &ctx));
1032   // Check if same
1033   {
1034     size_t    len_old, len_new;
1035     PetscBool is_same = PETSC_FALSE;
1036 
1037     PetscCall(PetscStrlen(ctx->coo_mat_type, &len_old));
1038     PetscCall(PetscStrlen(type, &len_new));
1039     if (len_old == len_new) PetscCall(PetscStrncmp(ctx->coo_mat_type, type, len_old, &is_same));
1040     if (is_same) PetscFunctionReturn(PETSC_SUCCESS);
1041   }
1042   // Clean up old mats in different format
1043   // LCOV_EXCL_START
1044   if (ctx->mat_assembled_full_internal) {
1045     for (PetscInt i = 0; i < ctx->num_mats_assembled_full; i++) {
1046       if (ctx->mats_assembled_full[i] == ctx->mat_assembled_full_internal) {
1047         for (PetscInt j = i + 1; j < ctx->num_mats_assembled_full; j++) {
1048           ctx->mats_assembled_full[j - 1] = ctx->mats_assembled_full[j];
1049         }
1050         ctx->num_mats_assembled_full--;
1051         // Note: we'll realloc this array again, so no need to shrink the allocation
1052         PetscCall(MatDestroy(&ctx->mat_assembled_full_internal));
1053       }
1054     }
1055   }
1056   if (ctx->mat_assembled_pbd_internal) {
1057     for (PetscInt i = 0; i < ctx->num_mats_assembled_pbd; i++) {
1058       if (ctx->mats_assembled_pbd[i] == ctx->mat_assembled_pbd_internal) {
1059         for (PetscInt j = i + 1; j < ctx->num_mats_assembled_pbd; j++) {
1060           ctx->mats_assembled_pbd[j - 1] = ctx->mats_assembled_pbd[j];
1061         }
1062         // Note: we'll realloc this array again, so no need to shrink the allocation
1063         ctx->num_mats_assembled_pbd--;
1064         PetscCall(MatDestroy(&ctx->mat_assembled_pbd_internal));
1065       }
1066     }
1067   }
1068   PetscCall(PetscFree(ctx->coo_mat_type));
1069   PetscCall(PetscStrallocpy(type, &ctx->coo_mat_type));
1070   PetscFunctionReturn(PETSC_SUCCESS);
1071   // LCOV_EXCL_STOP
1072 }
1073 
1074 /**
1075   @brief Gets the default COO matrix type as a string from the `MATCEED`.
1076 
1077   Collective across MPI processes.
1078 
1079   @param[in,out]  mat   `MATCEED`
1080   @param[in]      type  COO `MatType`
1081 
1082   @return An error code: 0 - success, otherwise - failure
1083 **/
1084 PetscErrorCode MatCeedGetCOOMatType(Mat mat, MatType *type) {
1085   MatCeedContext ctx;
1086 
1087   PetscFunctionBeginUser;
1088   PetscCall(MatShellGetContext(mat, &ctx));
1089   *type = ctx->coo_mat_type;
1090   PetscFunctionReturn(PETSC_SUCCESS);
1091 }
1092 
1093 /**
1094   @brief Set input local vectors for `MATCEED` `MatMult()` and `MatMultTranspose()` operations.
1095 
1096   Not collective across MPI processes.
1097 
1098   @param[in,out]  mat              `MATCEED`
1099   @param[in]      X_loc            Input PETSc local vector, or NULL
1100   @param[in]      Y_loc_transpose  Input PETSc local vector for transpose operation, or NULL
1101 
1102   @return An error code: 0 - success, otherwise - failure
1103 **/
1104 PetscErrorCode MatCeedSetLocalVectors(Mat mat, Vec X_loc, Vec Y_loc_transpose) {
1105   MatCeedContext ctx;
1106 
1107   PetscFunctionBeginUser;
1108   PetscCall(MatShellGetContext(mat, &ctx));
1109   if (X_loc) {
1110     PetscInt len_old, len_new;
1111 
1112     PetscCall(VecGetSize(ctx->X_loc, &len_old));
1113     PetscCall(VecGetSize(X_loc, &len_new));
1114     PetscCheck(len_old == len_new, PETSC_COMM_SELF, PETSC_ERR_LIB, "new X_loc length %" PetscInt_FMT " should match old X_loc length %" PetscInt_FMT,
1115                len_new, len_old);
1116     PetscCall(VecReferenceCopy(X_loc, &ctx->X_loc));
1117   }
1118   if (Y_loc_transpose) {
1119     PetscInt len_old, len_new;
1120 
1121     PetscCall(VecGetSize(ctx->Y_loc_transpose, &len_old));
1122     PetscCall(VecGetSize(Y_loc_transpose, &len_new));
1123     PetscCheck(len_old == len_new, PETSC_COMM_SELF, PETSC_ERR_LIB,
1124                "new Y_loc_transpose length %" PetscInt_FMT " should match old Y_loc_transpose length %" PetscInt_FMT, len_new, len_old);
1125     PetscCall(VecReferenceCopy(Y_loc_transpose, &ctx->Y_loc_transpose));
1126   }
1127   PetscFunctionReturn(PETSC_SUCCESS);
1128 }
1129 
1130 /**
1131   @brief Get input local vectors for `MATCEED` `MatMult()` and `MatMultTranspose()` operations.
1132 
1133   Not collective across MPI processes.
1134 
1135   @param[in,out]  mat              `MATCEED`
1136   @param[out]     X_loc            Input PETSc local vector, or NULL
1137   @param[out]     Y_loc_transpose  Input PETSc local vector for transpose operation, or NULL
1138 
1139   @return An error code: 0 - success, otherwise - failure
1140 **/
1141 PetscErrorCode MatCeedGetLocalVectors(Mat mat, Vec *X_loc, Vec *Y_loc_transpose) {
1142   MatCeedContext ctx;
1143 
1144   PetscFunctionBeginUser;
1145   PetscCall(MatShellGetContext(mat, &ctx));
1146   if (X_loc) {
1147     *X_loc = NULL;
1148     PetscCall(VecReferenceCopy(ctx->X_loc, X_loc));
1149   }
1150   if (Y_loc_transpose) {
1151     *Y_loc_transpose = NULL;
1152     PetscCall(VecReferenceCopy(ctx->Y_loc_transpose, Y_loc_transpose));
1153   }
1154   PetscFunctionReturn(PETSC_SUCCESS);
1155 }
1156 
1157 /**
1158   @brief Restore input local vectors for `MATCEED` `MatMult()` and `MatMultTranspose()` operations.
1159 
1160   Not collective across MPI processes.
1161 
1162   @param[in,out]  mat              MatCeed
1163   @param[out]     X_loc            Input PETSc local vector, or NULL
1164   @param[out]     Y_loc_transpose  Input PETSc local vector for transpose operation, or NULL
1165 
1166   @return An error code: 0 - success, otherwise - failure
1167 **/
1168 PetscErrorCode MatCeedRestoreLocalVectors(Mat mat, Vec *X_loc, Vec *Y_loc_transpose) {
1169   PetscFunctionBeginUser;
1170   if (X_loc) PetscCall(VecDestroy(X_loc));
1171   if (Y_loc_transpose) PetscCall(VecDestroy(Y_loc_transpose));
1172   PetscFunctionReturn(PETSC_SUCCESS);
1173 }
1174 
1175 /**
1176   @brief Get libCEED `CeedOperator` for `MATCEED` `MatMult()` and `MatMultTranspose()` operations.
1177 
1178   Not collective across MPI processes.
1179 
1180   @param[in,out]  mat                MatCeed
1181   @param[out]     op_mult            libCEED `CeedOperator` for `MatMult()`, or NULL
1182   @param[out]     op_mult_transpose  libCEED `CeedOperator` for `MatMultTranspose()`, or NULL
1183 
1184   @return An error code: 0 - success, otherwise - failure
1185 **/
1186 PetscErrorCode MatCeedGetCeedOperators(Mat mat, CeedOperator *op_mult, CeedOperator *op_mult_transpose) {
1187   MatCeedContext ctx;
1188 
1189   PetscFunctionBeginUser;
1190   PetscCall(MatShellGetContext(mat, &ctx));
1191   if (op_mult) {
1192     *op_mult = NULL;
1193     PetscCallCeed(ctx->ceed, CeedOperatorReferenceCopy(ctx->op_mult, op_mult));
1194   }
1195   if (op_mult_transpose) {
1196     *op_mult_transpose = NULL;
1197     PetscCallCeed(ctx->ceed, CeedOperatorReferenceCopy(ctx->op_mult_transpose, op_mult_transpose));
1198   }
1199   PetscFunctionReturn(PETSC_SUCCESS);
1200 }
1201 
1202 /**
1203   @brief Restore libCEED `CeedOperator` for `MATCEED` `MatMult()` and `MatMultTranspose()` operations.
1204 
1205   Not collective across MPI processes.
1206 
1207   @param[in,out]  mat                MatCeed
1208   @param[out]     op_mult            libCEED `CeedOperator` for `MatMult()`, or NULL
1209   @param[out]     op_mult_transpose  libCEED `CeedOperator` for `MatMultTranspose()`, or NULL
1210 
1211   @return An error code: 0 - success, otherwise - failure
1212 **/
1213 PetscErrorCode MatCeedRestoreCeedOperators(Mat mat, CeedOperator *op_mult, CeedOperator *op_mult_transpose) {
1214   MatCeedContext ctx;
1215 
1216   PetscFunctionBeginUser;
1217   PetscCall(MatShellGetContext(mat, &ctx));
1218   if (op_mult) PetscCallCeed(ctx->ceed, CeedOperatorDestroy(op_mult));
1219   if (op_mult_transpose) PetscCallCeed(ctx->ceed, CeedOperatorDestroy(op_mult_transpose));
1220   PetscFunctionReturn(PETSC_SUCCESS);
1221 }
1222 
1223 /**
1224   @brief Set `PetscLogEvent` for `MATCEED` `MatMult()` and `MatMultTranspose()` operators.
1225 
1226   Not collective across MPI processes.
1227 
1228   @param[in,out]  mat                       MatCeed
1229   @param[out]     log_event_mult            `PetscLogEvent` for forward evaluation, or NULL
1230   @param[out]     log_event_mult_transpose  `PetscLogEvent` for transpose evaluation, or NULL
1231 
1232   @return An error code: 0 - success, otherwise - failure
1233 **/
1234 PetscErrorCode MatCeedSetLogEvents(Mat mat, PetscLogEvent log_event_mult, PetscLogEvent log_event_mult_transpose) {
1235   MatCeedContext ctx;
1236 
1237   PetscFunctionBeginUser;
1238   PetscCall(MatShellGetContext(mat, &ctx));
1239   if (log_event_mult) ctx->log_event_mult = log_event_mult;
1240   if (log_event_mult_transpose) ctx->log_event_mult_transpose = log_event_mult_transpose;
1241   PetscFunctionReturn(PETSC_SUCCESS);
1242 }
1243 
1244 /**
1245   @brief Get `PetscLogEvent` for `MATCEED` `MatMult()` and `MatMultTranspose()` operators.
1246 
1247   Not collective across MPI processes.
1248 
1249   @param[in,out]  mat                       MatCeed
1250   @param[out]     log_event_mult            `PetscLogEvent` for forward evaluation, or NULL
1251   @param[out]     log_event_mult_transpose  `PetscLogEvent` for transpose evaluation, or NULL
1252 
1253   @return An error code: 0 - success, otherwise - failure
1254 **/
1255 PetscErrorCode MatCeedGetLogEvents(Mat mat, PetscLogEvent *log_event_mult, PetscLogEvent *log_event_mult_transpose) {
1256   MatCeedContext ctx;
1257 
1258   PetscFunctionBeginUser;
1259   PetscCall(MatShellGetContext(mat, &ctx));
1260   if (log_event_mult) *log_event_mult = ctx->log_event_mult;
1261   if (log_event_mult_transpose) *log_event_mult_transpose = ctx->log_event_mult_transpose;
1262   PetscFunctionReturn(PETSC_SUCCESS);
1263 }
1264 
1265 /**
1266   @brief Set `CeedOperator` `PetscLogEvent` for `MATCEED` `MatMult()` and `MatMultTranspose()` operators.
1267 
1268   Not collective across MPI processes.
1269 
1270   @param[in,out]  mat                       MatCeed
1271   @param[out]     log_event_mult            `PetscLogEvent` for forward `CeedOperator` evaluation, or NULL
1272   @param[out]     log_event_mult_transpose  `PetscLogEvent` for transpose `CeedOperator` evaluation, or NULL
1273 
1274   @return An error code: 0 - success, otherwise - failure
1275 **/
1276 PetscErrorCode MatCeedSetCeedOperatorLogEvents(Mat mat, PetscLogEvent log_event_mult, PetscLogEvent log_event_mult_transpose) {
1277   MatCeedContext ctx;
1278 
1279   PetscFunctionBeginUser;
1280   PetscCall(MatShellGetContext(mat, &ctx));
1281   if (log_event_mult) ctx->log_event_ceed_mult = log_event_mult;
1282   if (log_event_mult_transpose) ctx->log_event_ceed_mult_transpose = log_event_mult_transpose;
1283   PetscFunctionReturn(PETSC_SUCCESS);
1284 }
1285 
1286 /**
1287   @brief Get `CeedOperator` `PetscLogEvent` for `MATCEED` `MatMult()` and `MatMultTranspose()` operators.
1288 
1289   Not collective across MPI processes.
1290 
1291   @param[in,out]  mat                       MatCeed
1292   @param[out]     log_event_mult            `PetscLogEvent` for forward `CeedOperator` evaluation, or NULL
1293   @param[out]     log_event_mult_transpose  `PetscLogEvent` for transpose `CeedOperator` evaluation, or NULL
1294 
1295   @return An error code: 0 - success, otherwise - failure
1296 **/
1297 PetscErrorCode MatCeedGetCeedOperatorLogEvents(Mat mat, PetscLogEvent *log_event_mult, PetscLogEvent *log_event_mult_transpose) {
1298   MatCeedContext ctx;
1299 
1300   PetscFunctionBeginUser;
1301   PetscCall(MatShellGetContext(mat, &ctx));
1302   if (log_event_mult) *log_event_mult = ctx->log_event_ceed_mult;
1303   if (log_event_mult_transpose) *log_event_mult_transpose = ctx->log_event_ceed_mult_transpose;
1304   PetscFunctionReturn(PETSC_SUCCESS);
1305 }
1306 
1307 // -----------------------------------------------------------------------------
1308 // Operator context data
1309 // -----------------------------------------------------------------------------
1310 
1311 /**
1312   @brief Setup context data for operator application.
1313 
1314   Collective across MPI processes.
1315 
1316   @param[in]   dm_x                           Input `DM`
1317   @param[in]   dm_y                           Output `DM`
1318   @param[in]   X_loc                          Input PETSc local vector, or NULL
1319   @param[in]   Y_loc_transpose                Input PETSc local vector for transpose operation, or NULL
1320   @param[in]   op_mult                        `CeedOperator` for forward evaluation
1321   @param[in]   op_mult_transpose              `CeedOperator` for transpose evaluation
1322   @param[in]   log_event_mult                 `PetscLogEvent` for forward evaluation
1323   @param[in]   log_event_mult_transpose       `PetscLogEvent` for transpose evaluation
1324   @param[in]   log_event_ceed_mult            `PetscLogEvent` for forward `CeedOperator` evaluation
1325   @param[in]   log_event_ceed_mult_transpose  `PetscLogEvent` for transpose `CeedOperator` evaluation
1326   @param[out]  ctx                            Context data for operator evaluation
1327 
1328   @return An error code: 0 - success, otherwise - failure
1329 **/
1330 PetscErrorCode MatCeedContextCreate(DM dm_x, DM dm_y, Vec X_loc, Vec Y_loc_transpose, CeedOperator op_mult, CeedOperator op_mult_transpose,
1331                                     PetscLogEvent log_event_mult, PetscLogEvent log_event_mult_transpose, PetscLogEvent log_event_ceed_mult,
1332                                     PetscLogEvent log_event_ceed_mult_transpose, MatCeedContext *ctx) {
1333   CeedSize x_loc_len, y_loc_len;
1334 
1335   PetscFunctionBeginUser;
1336   // Allocate
1337   PetscCall(PetscNew(ctx));
1338   (*ctx)->ref_count = 1;
1339 
1340   // Logging
1341   (*ctx)->log_event_mult                = log_event_mult;
1342   (*ctx)->log_event_mult_transpose      = log_event_mult_transpose;
1343   (*ctx)->log_event_ceed_mult           = log_event_ceed_mult;
1344   (*ctx)->log_event_ceed_mult_transpose = log_event_ceed_mult_transpose;
1345 
1346   // PETSc objects
1347   PetscCall(DMReferenceCopy(dm_x, &(*ctx)->dm_x));
1348   PetscCall(DMReferenceCopy(dm_y, &(*ctx)->dm_y));
1349   if (X_loc) PetscCall(VecReferenceCopy(X_loc, &(*ctx)->X_loc));
1350   if (Y_loc_transpose) PetscCall(VecReferenceCopy(Y_loc_transpose, &(*ctx)->Y_loc_transpose));
1351 
1352   // Memtype
1353   {
1354     const PetscScalar *x;
1355     Vec                X;
1356 
1357     PetscCall(DMGetLocalVector(dm_x, &X));
1358     PetscCall(VecGetArrayReadAndMemType(X, &x, &(*ctx)->mem_type));
1359     PetscCall(VecRestoreArrayReadAndMemType(X, &x));
1360     PetscCall(DMRestoreLocalVector(dm_x, &X));
1361   }
1362 
1363   // libCEED objects
1364   PetscCheck(CeedOperatorGetCeed(op_mult, &(*ctx)->ceed) == CEED_ERROR_SUCCESS, PETSC_COMM_SELF, PETSC_ERR_LIB,
1365              "retrieving Ceed context object failed");
1366   PetscCallCeed((*ctx)->ceed, CeedOperatorGetActiveVectorLengths(op_mult, &x_loc_len, &y_loc_len));
1367   PetscCallCeed((*ctx)->ceed, CeedOperatorReferenceCopy(op_mult, &(*ctx)->op_mult));
1368   if (op_mult_transpose) PetscCallCeed((*ctx)->ceed, CeedOperatorReferenceCopy(op_mult_transpose, &(*ctx)->op_mult_transpose));
1369   PetscCallCeed((*ctx)->ceed, CeedVectorCreate((*ctx)->ceed, x_loc_len, &(*ctx)->x_loc));
1370   PetscCallCeed((*ctx)->ceed, CeedVectorCreate((*ctx)->ceed, y_loc_len, &(*ctx)->y_loc));
1371 
1372   // Flop counting
1373   {
1374     CeedSize ceed_flops_estimate = 0;
1375 
1376     PetscCallCeed((*ctx)->ceed, CeedOperatorGetFlopsEstimate(op_mult, &ceed_flops_estimate));
1377     (*ctx)->flops_mult = ceed_flops_estimate;
1378     if (op_mult_transpose) {
1379       PetscCallCeed((*ctx)->ceed, CeedOperatorGetFlopsEstimate(op_mult_transpose, &ceed_flops_estimate));
1380       (*ctx)->flops_mult_transpose = ceed_flops_estimate;
1381     }
1382   }
1383 
1384   // Check sizes
1385   if (x_loc_len > 0 || y_loc_len > 0) {
1386     CeedSize ctx_x_loc_len, ctx_y_loc_len;
1387     PetscInt X_loc_len, dm_x_loc_len, Y_loc_len, dm_y_loc_len;
1388     Vec      dm_X_loc, dm_Y_loc;
1389 
1390     // -- Input
1391     PetscCall(DMGetLocalVector(dm_x, &dm_X_loc));
1392     PetscCall(VecGetLocalSize(dm_X_loc, &dm_x_loc_len));
1393     PetscCall(DMRestoreLocalVector(dm_x, &dm_X_loc));
1394     PetscCallCeed((*ctx)->ceed, CeedVectorGetLength((*ctx)->x_loc, &ctx_x_loc_len));
1395     if (X_loc) {
1396       PetscCall(VecGetLocalSize(X_loc, &X_loc_len));
1397       PetscCheck(X_loc_len == dm_x_loc_len, PETSC_COMM_SELF, PETSC_ERR_LIB,
1398                  "X_loc (%" PetscInt_FMT ") must match dm_x (%" PetscInt_FMT ") dimensions", X_loc_len, dm_x_loc_len);
1399     }
1400     PetscCheck(x_loc_len == dm_x_loc_len, PETSC_COMM_SELF, PETSC_ERR_LIB, "op (%" CeedSize_FMT ") must match dm_x (%" PetscInt_FMT ") dimensions",
1401                x_loc_len, dm_x_loc_len);
1402     PetscCheck(x_loc_len == ctx_x_loc_len, PETSC_COMM_SELF, PETSC_ERR_LIB, "x_loc (%" CeedSize_FMT ") must match op dimensions (%" CeedSize_FMT ")",
1403                x_loc_len, ctx_x_loc_len);
1404 
1405     // -- Output
1406     PetscCall(DMGetLocalVector(dm_y, &dm_Y_loc));
1407     PetscCall(VecGetLocalSize(dm_Y_loc, &dm_y_loc_len));
1408     PetscCall(DMRestoreLocalVector(dm_y, &dm_Y_loc));
1409     PetscCallCeed((*ctx)->ceed, CeedVectorGetLength((*ctx)->y_loc, &ctx_y_loc_len));
1410     PetscCheck(ctx_y_loc_len == dm_y_loc_len, PETSC_COMM_SELF, PETSC_ERR_LIB, "op (%" CeedSize_FMT ") must match dm_y (%" PetscInt_FMT ") dimensions",
1411                ctx_y_loc_len, dm_y_loc_len);
1412 
1413     // -- Transpose
1414     if (Y_loc_transpose) {
1415       PetscCall(VecGetLocalSize(Y_loc_transpose, &Y_loc_len));
1416       PetscCheck(Y_loc_len == dm_y_loc_len, PETSC_COMM_SELF, PETSC_ERR_LIB,
1417                  "Y_loc_transpose (%" PetscInt_FMT ") must match dm_y (%" PetscInt_FMT ") dimensions", Y_loc_len, dm_y_loc_len);
1418     }
1419   }
1420   PetscFunctionReturn(PETSC_SUCCESS);
1421 }
1422 
1423 /**
1424   @brief Increment reference counter for `MATCEED` context.
1425 
1426   Not collective across MPI processes.
1427 
1428   @param[in,out]  ctx  Context data
1429 
1430   @return An error code: 0 - success, otherwise - failure
1431 **/
1432 PetscErrorCode MatCeedContextReference(MatCeedContext ctx) {
1433   PetscFunctionBeginUser;
1434   ctx->ref_count++;
1435   PetscFunctionReturn(PETSC_SUCCESS);
1436 }
1437 
1438 /**
1439   @brief Copy reference for `MATCEED`.
1440          Note: If `ctx_copy` is non-null, it is assumed to be a valid pointer to a `MatCeedContext`.
1441 
1442   Not collective across MPI processes.
1443 
1444   @param[in]   ctx       Context data
1445   @param[out]  ctx_copy  Copy of pointer to context data
1446 
1447   @return An error code: 0 - success, otherwise - failure
1448 **/
1449 PetscErrorCode MatCeedContextReferenceCopy(MatCeedContext ctx, MatCeedContext *ctx_copy) {
1450   PetscFunctionBeginUser;
1451   PetscCall(MatCeedContextReference(ctx));
1452   PetscCall(MatCeedContextDestroy(ctx_copy));
1453   *ctx_copy = ctx;
1454   PetscFunctionReturn(PETSC_SUCCESS);
1455 }
1456 
1457 /**
1458   @brief Destroy context data for operator application.
1459 
1460   Collective across MPI processes.
1461 
1462   @param[in,out]  ctx  Context data for operator evaluation
1463 
1464   @return An error code: 0 - success, otherwise - failure
1465 **/
1466 PetscErrorCode MatCeedContextDestroy(MatCeedContext *ctx) {
1467   PetscFunctionBeginUser;
1468   if (!ctx || --(*ctx)->ref_count > 0) PetscFunctionReturn(PETSC_SUCCESS);
1469 
1470   // PETSc objects
1471   PetscCall(DMDestroy(&(*ctx)->dm_x));
1472   PetscCall(DMDestroy(&(*ctx)->dm_y));
1473   PetscCall(VecDestroy(&(*ctx)->X_loc));
1474   PetscCall(VecDestroy(&(*ctx)->Y_loc_transpose));
1475   PetscCall(MatDestroy(&(*ctx)->mat_assembled_full_internal));
1476   PetscCall(MatDestroy(&(*ctx)->mat_assembled_pbd_internal));
1477   PetscCall(PetscFree((*ctx)->coo_mat_type));
1478   PetscCall(PetscFree((*ctx)->mats_assembled_full));
1479   PetscCall(PetscFree((*ctx)->mats_assembled_pbd));
1480 
1481   // libCEED objects
1482   PetscCallCeed((*ctx)->ceed, CeedVectorDestroy(&(*ctx)->x_loc));
1483   PetscCallCeed((*ctx)->ceed, CeedVectorDestroy(&(*ctx)->y_loc));
1484   PetscCallCeed((*ctx)->ceed, CeedVectorDestroy(&(*ctx)->coo_values_full));
1485   PetscCallCeed((*ctx)->ceed, CeedVectorDestroy(&(*ctx)->coo_values_pbd));
1486   PetscCallCeed((*ctx)->ceed, CeedOperatorDestroy(&(*ctx)->op_mult));
1487   PetscCallCeed((*ctx)->ceed, CeedOperatorDestroy(&(*ctx)->op_mult_transpose));
1488   PetscCheck(CeedDestroy(&(*ctx)->ceed) == CEED_ERROR_SUCCESS, PETSC_COMM_SELF, PETSC_ERR_LIB, "destroying libCEED context object failed");
1489 
1490   // Deallocate
1491   (*ctx)->is_destroyed = PETSC_TRUE;  // Flag as destroyed in case someone has stale ref
1492   PetscCall(PetscFree(*ctx));
1493   PetscFunctionReturn(PETSC_SUCCESS);
1494 }
1495 
1496 /**
1497   @brief Compute the diagonal of an operator via libCEED.
1498 
1499   Collective across MPI processes.
1500 
1501   @param[in]   A  `MATCEED`
1502   @param[out]  D  Vector holding operator diagonal
1503 
1504   @return An error code: 0 - success, otherwise - failure
1505 **/
1506 PetscErrorCode MatGetDiagonal_Ceed(Mat A, Vec D) {
1507   PetscMemType   mem_type;
1508   Vec            D_loc;
1509   MatCeedContext ctx;
1510 
1511   PetscFunctionBeginUser;
1512   PetscCall(MatShellGetContext(A, &ctx));
1513 
1514   // Place PETSc vector in libCEED vector
1515   PetscCall(PetscLogEventBegin(MATCEED_ASSEMBLE_DIAGONAL, A, D, NULL, NULL));
1516   PetscCall(DMGetLocalVector(ctx->dm_x, &D_loc));
1517   PetscCall(VecPetscToCeed(D_loc, &mem_type, ctx->x_loc));
1518 
1519   // Compute Diagonal
1520   PetscCall(PetscLogEventBegin(MATCEED_ASSEMBLE_DIAGONAL_CEEDOP, A, D, NULL, NULL));
1521   PetscCallCeed(ctx->ceed, CeedOperatorLinearAssembleDiagonal(ctx->op_mult, ctx->x_loc, CEED_REQUEST_IMMEDIATE));
1522   PetscCall(PetscLogEventEnd(MATCEED_ASSEMBLE_DIAGONAL_CEEDOP, A, D, NULL, NULL));
1523 
1524   // Restore PETSc vector
1525   PetscCall(VecCeedToPetsc(ctx->x_loc, mem_type, D_loc));
1526 
1527   // Local-to-Global
1528   PetscCall(VecZeroEntries(D));
1529   PetscCall(DMLocalToGlobal(ctx->dm_x, D_loc, ADD_VALUES, D));
1530   PetscCall(DMRestoreLocalVector(ctx->dm_x, &D_loc));
1531   PetscCall(PetscLogEventEnd(MATCEED_ASSEMBLE_DIAGONAL, A, D, NULL, NULL));
1532   PetscFunctionReturn(PETSC_SUCCESS);
1533 }
1534 
1535 /**
1536   @brief Compute `A X = Y` for a `MATCEED`.
1537 
1538   Collective across MPI processes.
1539 
1540   @param[in]   A  `MATCEED`
1541   @param[in]   X  Input PETSc vector
1542   @param[out]  Y  Output PETSc vector
1543 
1544   @return An error code: 0 - success, otherwise - failure
1545 **/
1546 PetscErrorCode MatMult_Ceed(Mat A, Vec X, Vec Y) {
1547   MatCeedContext ctx;
1548 
1549   PetscFunctionBeginUser;
1550   PetscCall(MatShellGetContext(A, &ctx));
1551   PetscCall(PetscLogEventBegin(ctx->log_event_mult, A, X, Y, NULL));
1552 
1553   {
1554     PetscMemType x_mem_type, y_mem_type;
1555     Vec          X_loc = ctx->X_loc, Y_loc;
1556 
1557     // Get local vectors
1558     if (!ctx->X_loc) PetscCall(DMGetLocalVector(ctx->dm_x, &X_loc));
1559     PetscCall(DMGetLocalVector(ctx->dm_y, &Y_loc));
1560 
1561     // Global-to-local
1562     PetscCall(DMGlobalToLocal(ctx->dm_x, X, INSERT_VALUES, X_loc));
1563 
1564     // Setup libCEED vectors
1565     PetscCall(VecReadPetscToCeed(X_loc, &x_mem_type, ctx->x_loc));
1566     PetscCall(VecZeroEntries(Y_loc));
1567     PetscCall(VecPetscToCeed(Y_loc, &y_mem_type, ctx->y_loc));
1568 
1569     // Apply libCEED operator
1570     PetscCall(PetscLogEventBegin(ctx->log_event_ceed_mult, A, X, Y, NULL));
1571     PetscCall(PetscLogGpuTimeBegin());
1572     PetscCallCeed(ctx->ceed, CeedOperatorApplyAdd(ctx->op_mult, ctx->x_loc, ctx->y_loc, CEED_REQUEST_IMMEDIATE));
1573     // Log flops
1574     if (PetscMemTypeDevice(ctx->mem_type)) PetscCall(PetscLogGpuFlops(ctx->flops_mult));
1575     else PetscCall(PetscLogFlops(ctx->flops_mult));
1576     PetscCall(PetscLogGpuTimeEnd());
1577     PetscCall(PetscLogEventEnd(ctx->log_event_ceed_mult, A, X, Y, NULL));
1578 
1579     // Restore PETSc vectors
1580     PetscCall(VecReadCeedToPetsc(ctx->x_loc, x_mem_type, X_loc));
1581     PetscCall(VecCeedToPetsc(ctx->y_loc, y_mem_type, Y_loc));
1582 
1583     // Local-to-global
1584     PetscCall(VecZeroEntries(Y));
1585     PetscCall(DMLocalToGlobal(ctx->dm_y, Y_loc, ADD_VALUES, Y));
1586 
1587     // Restore local vectors, as needed
1588     if (!ctx->X_loc) PetscCall(DMRestoreLocalVector(ctx->dm_x, &X_loc));
1589     PetscCall(DMRestoreLocalVector(ctx->dm_y, &Y_loc));
1590   }
1591 
1592   PetscCall(PetscLogEventEnd(ctx->log_event_mult, A, X, Y, NULL));
1593   PetscFunctionReturn(PETSC_SUCCESS);
1594 }
1595 
1596 /**
1597   @brief Compute `A^T Y = X` for a `MATCEED`.
1598 
1599   Collective across MPI processes.
1600 
1601   @param[in]   A  `MATCEED`
1602   @param[in]   Y  Input PETSc vector
1603   @param[out]  X  Output PETSc vector
1604 
1605   @return An error code: 0 - success, otherwise - failure
1606 **/
1607 PetscErrorCode MatMultTranspose_Ceed(Mat A, Vec Y, Vec X) {
1608   MatCeedContext ctx;
1609 
1610   PetscFunctionBeginUser;
1611   PetscCall(MatShellGetContext(A, &ctx));
1612   PetscCall(PetscLogEventBegin(ctx->log_event_mult_transpose, A, Y, X, NULL));
1613 
1614   {
1615     PetscMemType x_mem_type, y_mem_type;
1616     Vec          X_loc, Y_loc = ctx->Y_loc_transpose;
1617 
1618     // Get local vectors
1619     if (!ctx->Y_loc_transpose) PetscCall(DMGetLocalVector(ctx->dm_y, &Y_loc));
1620     PetscCall(DMGetLocalVector(ctx->dm_x, &X_loc));
1621 
1622     // Global-to-local
1623     PetscCall(DMGlobalToLocal(ctx->dm_y, Y, INSERT_VALUES, Y_loc));
1624 
1625     // Setup libCEED vectors
1626     PetscCall(VecReadPetscToCeed(Y_loc, &y_mem_type, ctx->y_loc));
1627     PetscCall(VecZeroEntries(X_loc));
1628     PetscCall(VecPetscToCeed(X_loc, &x_mem_type, ctx->x_loc));
1629 
1630     // Apply libCEED operator
1631     PetscCall(PetscLogEventBegin(ctx->log_event_ceed_mult_transpose, A, Y, X, NULL));
1632     PetscCall(PetscLogGpuTimeBegin());
1633     PetscCallCeed(ctx->ceed, CeedOperatorApplyAdd(ctx->op_mult_transpose, ctx->y_loc, ctx->x_loc, CEED_REQUEST_IMMEDIATE));
1634     // Log flops
1635     if (PetscMemTypeDevice(ctx->mem_type)) PetscCall(PetscLogGpuFlops(ctx->flops_mult_transpose));
1636     else PetscCall(PetscLogFlops(ctx->flops_mult_transpose));
1637     PetscCall(PetscLogGpuTimeEnd());
1638     PetscCall(PetscLogEventEnd(ctx->log_event_ceed_mult_transpose, A, Y, X, NULL));
1639 
1640     // Restore PETSc vectors
1641     PetscCall(VecReadCeedToPetsc(ctx->y_loc, y_mem_type, Y_loc));
1642     PetscCall(VecCeedToPetsc(ctx->x_loc, x_mem_type, X_loc));
1643 
1644     // Local-to-global
1645     PetscCall(VecZeroEntries(X));
1646     PetscCall(DMLocalToGlobal(ctx->dm_x, X_loc, ADD_VALUES, X));
1647 
1648     // Restore local vectors, as needed
1649     if (!ctx->Y_loc_transpose) PetscCall(DMRestoreLocalVector(ctx->dm_y, &Y_loc));
1650     PetscCall(DMRestoreLocalVector(ctx->dm_x, &X_loc));
1651   }
1652 
1653   PetscCall(PetscLogEventEnd(ctx->log_event_mult_transpose, A, Y, X, NULL));
1654   PetscFunctionReturn(PETSC_SUCCESS);
1655 }
1656