1 // Copyright (c) 2017, Lawrence Livermore National Security, LLC. Produced at 2 // the Lawrence Livermore National Laboratory. LLNL-CODE-734707. All Rights 3 // reserved. See files LICENSE and NOTICE for details. 4 // 5 // This file is part of CEED, a collection of benchmarks, miniapps, software 6 // libraries and APIs for efficient high-order finite element and spectral 7 // element discretizations for exascale applications. For more information and 8 // source code availability see http://github.com/ceed. 9 // 10 // The CEED research is supported by the Exascale Computing Project 17-SC-20-SC, 11 // a collaborative effort of two U.S. Department of Energy organizations (Office 12 // of Science and the National Nuclear Security Administration) responsible for 13 // the planning and preparation of a capable exascale ecosystem, including 14 // software, applications, hardware, advanced system engineering and early 15 // testbed platforms, in support of the nation's exascale computing imperative. 16 17 #include <ceed-impl.h> 18 #include <ceed-backend.h> 19 #include <string.h> 20 #include <math.h> 21 22 /// @file 23 /// Implementation of public CeedOperator interfaces 24 /// 25 /// @addtogroup CeedOperator 26 /// @{ 27 28 /** 29 @brief Create a CeedOperator and associate a CeedQFunction. A CeedBasis and 30 CeedElemRestriction can be associated with CeedQFunction fields with 31 \ref CeedOperatorSetField. 32 33 @param ceed A Ceed object where the CeedOperator will be created 34 @param qf QFunction defining the action of the operator at quadrature points 35 @param dqf QFunction defining the action of the Jacobian of @a qf (or 36 CEED_QFUNCTION_NONE) 37 @param dqfT QFunction defining the action of the transpose of the Jacobian 38 of @a qf (or CEED_QFUNCTION_NONE) 39 @param[out] op Address of the variable where the newly created 40 CeedOperator will be stored 41 42 @return An error code: 0 - success, otherwise - failure 43 44 @ref Basic 45 */ 46 int CeedOperatorCreate(Ceed ceed, CeedQFunction qf, CeedQFunction dqf, 47 CeedQFunction dqfT, CeedOperator *op) { 48 int ierr; 49 50 if (!ceed->OperatorCreate) { 51 Ceed delegate; 52 ierr = CeedGetObjectDelegate(ceed, &delegate, "Operator"); CeedChk(ierr); 53 54 if (!delegate) 55 // LCOV_EXCL_START 56 return CeedError(ceed, 1, "Backend does not support OperatorCreate"); 57 // LCOV_EXCL_STOP 58 59 ierr = CeedOperatorCreate(delegate, qf, dqf, dqfT, op); CeedChk(ierr); 60 return 0; 61 } 62 63 ierr = CeedCalloc(1, op); CeedChk(ierr); 64 (*op)->ceed = ceed; 65 ceed->refcount++; 66 (*op)->refcount = 1; 67 if (qf == CEED_QFUNCTION_NONE) 68 // LCOV_EXCL_START 69 return CeedError(ceed, 1, "Operator must have a valid QFunction."); 70 // LCOV_EXCL_STOP 71 (*op)->qf = qf; 72 qf->refcount++; 73 if (dqf && dqf != CEED_QFUNCTION_NONE) { 74 (*op)->dqf = dqf; 75 dqf->refcount++; 76 } 77 if (dqfT && dqfT != CEED_QFUNCTION_NONE) { 78 (*op)->dqfT = dqfT; 79 dqfT->refcount++; 80 } 81 ierr = CeedCalloc(16, &(*op)->inputfields); CeedChk(ierr); 82 ierr = CeedCalloc(16, &(*op)->outputfields); CeedChk(ierr); 83 ierr = ceed->OperatorCreate(*op); CeedChk(ierr); 84 return 0; 85 } 86 87 /** 88 @brief Create an operator that composes the action of several operators 89 90 @param ceed A Ceed object where the CeedOperator will be created 91 @param[out] op Address of the variable where the newly created 92 Composite CeedOperator will be stored 93 94 @return An error code: 0 - success, otherwise - failure 95 96 @ref Basic 97 */ 98 int CeedCompositeOperatorCreate(Ceed ceed, CeedOperator *op) { 99 int ierr; 100 101 if (!ceed->CompositeOperatorCreate) { 102 Ceed delegate; 103 ierr = CeedGetObjectDelegate(ceed, &delegate, "Operator"); CeedChk(ierr); 104 105 if (delegate) { 106 ierr = CeedCompositeOperatorCreate(delegate, op); CeedChk(ierr); 107 return 0; 108 } 109 } 110 111 ierr = CeedCalloc(1, op); CeedChk(ierr); 112 (*op)->ceed = ceed; 113 ceed->refcount++; 114 (*op)->composite = true; 115 ierr = CeedCalloc(16, &(*op)->suboperators); CeedChk(ierr); 116 117 if (ceed->CompositeOperatorCreate) { 118 ierr = ceed->CompositeOperatorCreate(*op); CeedChk(ierr); 119 } 120 return 0; 121 } 122 123 /** 124 @brief Provide a field to a CeedOperator for use by its CeedQFunction 125 126 This function is used to specify both active and passive fields to a 127 CeedOperator. For passive fields, a vector @arg v must be provided. Passive 128 fields can inputs or outputs (updated in-place when operator is applied). 129 130 Active fields must be specified using this function, but their data (in a 131 CeedVector) is passed in CeedOperatorApply(). There can be at most one active 132 input and at most one active output. 133 134 @param op CeedOperator on which to provide the field 135 @param fieldname Name of the field (to be matched with the name used by 136 CeedQFunction) 137 @param r CeedElemRestriction 138 @param lmode CeedTransposeMode which specifies the ordering of the 139 components of the l-vector used by this CeedOperatorField, 140 CEED_NOTRANSPOSE indicates the component is the 141 outermost index and CEED_TRANSPOSE indicates the component 142 is the innermost index in ordering of the l-vector 143 @param b CeedBasis in which the field resides or CEED_BASIS_COLLOCATED 144 if collocated with quadrature points 145 @param v CeedVector to be used by CeedOperator or CEED_VECTOR_ACTIVE 146 if field is active or CEED_VECTOR_NONE if using 147 CEED_EVAL_WEIGHT in the QFunction 148 149 @return An error code: 0 - success, otherwise - failure 150 151 @ref Basic 152 **/ 153 int CeedOperatorSetField(CeedOperator op, const char *fieldname, 154 CeedElemRestriction r, CeedTransposeMode lmode, 155 CeedBasis b, CeedVector v) { 156 int ierr; 157 if (op->composite) 158 // LCOV_EXCL_START 159 return CeedError(op->ceed, 1, "Cannot add field to composite operator."); 160 // LCOV_EXCL_STOP 161 if (!r) 162 // LCOV_EXCL_START 163 return CeedError(op->ceed, 1, 164 "ElemRestriction r for field \"%s\" must be non-NULL.", 165 fieldname); 166 // LCOV_EXCL_STOP 167 if (!b) 168 // LCOV_EXCL_START 169 return CeedError(op->ceed, 1, "Basis b for field \"%s\" must be non-NULL.", 170 fieldname); 171 // LCOV_EXCL_STOP 172 if (!v) 173 // LCOV_EXCL_START 174 return CeedError(op->ceed, 1, "Vector v for field \"%s\" must be non-NULL.", 175 fieldname); 176 // LCOV_EXCL_STOP 177 178 CeedInt numelements; 179 ierr = CeedElemRestrictionGetNumElements(r, &numelements); CeedChk(ierr); 180 if (op->hasrestriction && op->numelements != numelements) 181 // LCOV_EXCL_START 182 return CeedError(op->ceed, 1, 183 "ElemRestriction with %d elements incompatible with prior " 184 "%d elements", numelements, op->numelements); 185 // LCOV_EXCL_STOP 186 op->numelements = numelements; 187 op->hasrestriction = true; // Restriction set, but numelements may be 0 188 189 if (b != CEED_BASIS_COLLOCATED) { 190 CeedInt numqpoints; 191 ierr = CeedBasisGetNumQuadraturePoints(b, &numqpoints); CeedChk(ierr); 192 if (op->numqpoints && op->numqpoints != numqpoints) 193 // LCOV_EXCL_START 194 return CeedError(op->ceed, 1, "Basis with %d quadrature points " 195 "incompatible with prior %d points", numqpoints, 196 op->numqpoints); 197 // LCOV_EXCL_STOP 198 op->numqpoints = numqpoints; 199 } 200 CeedOperatorField *ofield; 201 for (CeedInt i=0; i<op->qf->numinputfields; i++) { 202 if (!strcmp(fieldname, (*op->qf->inputfields[i]).fieldname)) { 203 ofield = &op->inputfields[i]; 204 goto found; 205 } 206 } 207 for (CeedInt i=0; i<op->qf->numoutputfields; i++) { 208 if (!strcmp(fieldname, (*op->qf->outputfields[i]).fieldname)) { 209 ofield = &op->outputfields[i]; 210 goto found; 211 } 212 } 213 // LCOV_EXCL_START 214 return CeedError(op->ceed, 1, "QFunction has no knowledge of field '%s'", 215 fieldname); 216 // LCOV_EXCL_STOP 217 found: 218 ierr = CeedCalloc(1, ofield); CeedChk(ierr); 219 (*ofield)->Erestrict = r; 220 r->refcount += 1; 221 (*ofield)->lmode = lmode; 222 (*ofield)->basis = b; 223 if (b != CEED_BASIS_COLLOCATED) 224 b->refcount += 1; 225 (*ofield)->vec = v; 226 if (v != CEED_VECTOR_ACTIVE && v != CEED_VECTOR_NONE) 227 v->refcount += 1; 228 op->nfields += 1; 229 return 0; 230 } 231 232 /** 233 @brief Add a sub-operator to a composite CeedOperator 234 235 @param[out] compositeop Composite CeedOperator 236 @param subop Sub-operator CeedOperator 237 238 @return An error code: 0 - success, otherwise - failure 239 240 @ref Basic 241 */ 242 int CeedCompositeOperatorAddSub(CeedOperator compositeop, CeedOperator subop) { 243 if (!compositeop->composite) 244 // LCOV_EXCL_START 245 return CeedError(compositeop->ceed, 1, "CeedOperator is not a composite " 246 "operator"); 247 // LCOV_EXCL_STOP 248 249 if (compositeop->numsub == CEED_COMPOSITE_MAX) 250 // LCOV_EXCL_START 251 return CeedError(compositeop->ceed, 1, "Cannot add additional suboperators"); 252 // LCOV_EXCL_STOP 253 254 compositeop->suboperators[compositeop->numsub] = subop; 255 subop->refcount++; 256 compositeop->numsub++; 257 return 0; 258 } 259 260 /** 261 @brief Duplicate a CeedOperator with a reference Ceed to fallback for advanced 262 CeedOperator functionality 263 264 @param op CeedOperator to create fallback for 265 266 @return An error code: 0 - success, otherwise - failure 267 268 @ref Developer 269 **/ 270 int CeedOperatorCreateFallback(CeedOperator op) { 271 int ierr; 272 273 // Fallback Ceed 274 const char *resource, *fallbackresource; 275 ierr = CeedGetResource(op->ceed, &resource); CeedChk(ierr); 276 ierr = CeedGetOperatorFallbackResource(op->ceed, &fallbackresource); 277 CeedChk(ierr); 278 if (!strcmp(resource, fallbackresource)) 279 // LCOV_EXCL_START 280 return CeedError(op->ceed, 1, "Backend %s cannot create an operator" 281 "fallback to resource %s", resource, fallbackresource); 282 // LCOV_EXCL_STOP 283 284 Ceed ceedref; 285 ierr = CeedInit(fallbackresource, &ceedref); CeedChk(ierr); 286 ceedref->opfallbackparent = op->ceed; 287 op->ceed->opfallbackceed = ceedref; 288 289 // Clone Op 290 CeedOperator opref; 291 ierr = CeedCalloc(1, &opref); CeedChk(ierr); 292 memcpy(opref, op, sizeof(*opref)); CeedChk(ierr); 293 opref->data = NULL; 294 opref->setupdone = 0; 295 opref->ceed = ceedref; 296 ierr = ceedref->OperatorCreate(opref); CeedChk(ierr); 297 op->opfallback = opref; 298 299 // Clone QF 300 CeedQFunction qfref; 301 ierr = CeedCalloc(1, &qfref); CeedChk(ierr); 302 memcpy(qfref, (op->qf), sizeof(*qfref)); CeedChk(ierr); 303 qfref->data = NULL; 304 qfref->ceed = ceedref; 305 ierr = ceedref->QFunctionCreate(qfref); CeedChk(ierr); 306 opref->qf = qfref; 307 op->qffallback = qfref; 308 309 return 0; 310 } 311 312 /** 313 @brief Check if a CeedOperator is ready to be used. 314 315 @param[in] ceed Ceed object for error handling 316 @param[in] op CeedOperator to check 317 318 @return An error code: 0 - success, otherwise - failure 319 320 @ref Basic 321 **/ 322 static int CeedOperatorCheckReady(Ceed ceed, CeedOperator op) { 323 CeedQFunction qf = op->qf; 324 325 if (op->composite) { 326 if (!op->numsub) 327 // LCOV_EXCL_START 328 return CeedError(ceed, 1, "No suboperators set"); 329 // LCOV_EXCL_STOP 330 } else { 331 if (op->nfields == 0) 332 // LCOV_EXCL_START 333 return CeedError(ceed, 1, "No operator fields set"); 334 // LCOV_EXCL_STOP 335 if (op->nfields < qf->numinputfields + qf->numoutputfields) 336 // LCOV_EXCL_START 337 return CeedError(ceed, 1, "Not all operator fields set"); 338 // LCOV_EXCL_STOP 339 if (!op->hasrestriction) 340 // LCOV_EXCL_START 341 return CeedError(ceed, 1,"At least one restriction required"); 342 // LCOV_EXCL_STOP 343 if (op->numqpoints == 0) 344 // LCOV_EXCL_START 345 return CeedError(ceed, 1,"At least one non-collocated basis required"); 346 // LCOV_EXCL_STOP 347 } 348 349 return 0; 350 } 351 352 /** 353 @brief Assemble a linear CeedQFunction associated with a CeedOperator 354 355 This returns a CeedVector containing a matrix at each quadrature point 356 providing the action of the CeedQFunction associated with the CeedOperator. 357 The vector 'assembled' is of shape 358 [num_elements, num_input_fields, num_output_fields, num_quad_points] 359 and contains column-major matrices representing the action of the 360 CeedQFunction for a corresponding quadrature point on an element. Inputs and 361 outputs are in the order provided by the user when adding CeedOperator fields. 362 For example, a QFunction with inputs 'u' and 'gradu' and outputs 'gradv' and 363 'v', provided in that order, would result in an assembled QFunction that 364 consists of (1 + dim) x (dim + 1) matrices at each quadrature point acting 365 on the input [u, du_0, du_1] and producing the output [dv_0, dv_1, v]. 366 367 @param op CeedOperator to assemble CeedQFunction 368 @param[out] assembled CeedVector to store assembled CeedQFunction at 369 quadrature points 370 @param[out] rstr CeedElemRestriction for CeedVector containing assembled 371 CeedQFunction 372 @param request Address of CeedRequest for non-blocking completion, else 373 CEED_REQUEST_IMMEDIATE 374 375 @return An error code: 0 - success, otherwise - failure 376 377 @ref Basic 378 **/ 379 int CeedOperatorAssembleLinearQFunction(CeedOperator op, CeedVector *assembled, 380 CeedElemRestriction *rstr, 381 CeedRequest *request) { 382 int ierr; 383 Ceed ceed = op->ceed; 384 ierr = CeedOperatorCheckReady(ceed, op); CeedChk(ierr); 385 386 // Backend version 387 if (op->AssembleLinearQFunction) { 388 ierr = op->AssembleLinearQFunction(op, assembled, rstr, request); 389 CeedChk(ierr); 390 } else { 391 // Fallback to reference Ceed 392 if (!op->opfallback) { 393 ierr = CeedOperatorCreateFallback(op); CeedChk(ierr); 394 } 395 // Assemble 396 ierr = op->opfallback->AssembleLinearQFunction(op->opfallback, assembled, 397 rstr, request); CeedChk(ierr); 398 } 399 400 return 0; 401 } 402 403 /** 404 @brief Assemble the diagonal of a square linear Operator 405 406 This returns a CeedVector containing the diagonal of a linear CeedOperator. 407 408 @param op CeedOperator to assemble CeedQFunction 409 @param[out] assembled CeedVector to store assembled CeedOperator diagonal 410 @param request Address of CeedRequest for non-blocking completion, else 411 CEED_REQUEST_IMMEDIATE 412 413 @return An error code: 0 - success, otherwise - failure 414 415 @ref Basic 416 **/ 417 int CeedOperatorAssembleLinearDiagonal(CeedOperator op, CeedVector *assembled, 418 CeedRequest *request) { 419 int ierr; 420 Ceed ceed = op->ceed; 421 ierr = CeedOperatorCheckReady(ceed, op); CeedChk(ierr); 422 423 // Use backend version, if available 424 if (op->AssembleLinearDiagonal) { 425 ierr = op->AssembleLinearDiagonal(op, assembled, request); CeedChk(ierr); 426 } else { 427 // Fallback to reference Ceed 428 if (!op->opfallback) { 429 ierr = CeedOperatorCreateFallback(op); CeedChk(ierr); 430 } 431 // Assemble 432 ierr = op->opfallback->AssembleLinearDiagonal(op->opfallback, assembled, 433 request); CeedChk(ierr); 434 } 435 436 return 0; 437 } 438 439 /** 440 @brief Build a FDM based approximate inverse for each element for a 441 CeedOperator 442 443 This returns a CeedOperator and CeedVector to apply a Fast Diagonalization 444 Method based approximate inverse. This function obtains the simultaneous 445 diagonalization for the 1D mass and Laplacian operators, 446 M = V^T V, K = V^T S V. 447 The assembled QFunction is used to modify the eigenvalues from simultaneous 448 diagonalization and obtain an approximate inverse of the form 449 V^T S^hat V. The CeedOperator must be linear and non-composite. The 450 associated CeedQFunction must therefore also be linear. 451 452 @param op CeedOperator to create element inverses 453 @param[out] fdminv CeedOperator to apply the action of a FDM based inverse 454 for each element 455 @param[out] qdata CeedVector to hold qdata for fdminv 456 @param request Address of CeedRequest for non-blocking completion, else 457 CEED_REQUEST_IMMEDIATE 458 459 @return An error code: 0 - success, otherwise - failure 460 461 @ref Advanced 462 **/ 463 int CeedOperatorCreateFDMElementInverse(CeedOperator op, CeedOperator *fdminv, 464 CeedRequest *request) { 465 int ierr; 466 Ceed ceed = op->ceed; 467 468 // Determine active input basis 469 bool interp = false, grad = false; 470 CeedBasis basis = NULL; 471 CeedElemRestriction rstr = NULL; 472 for (CeedInt i=0; i<op->qf->numinputfields; i++) 473 if (op->inputfields[i] && op->inputfields[i]->vec == CEED_VECTOR_ACTIVE) { 474 basis = op->inputfields[i]->basis; 475 interp = interp || op->qf->inputfields[i]->emode == CEED_EVAL_INTERP; 476 grad = grad || op->qf->inputfields[i]->emode == CEED_EVAL_GRAD; 477 rstr = op->inputfields[i]->Erestrict; 478 } 479 if (!basis) 480 return CeedError(ceed, 1, "No active field set"); 481 CeedInt P1d, Q1d, elemsize, nqpts, dim, ncomp = 1, nelem = 1, nnodes = 1; 482 ierr = CeedBasisGetNumNodes1D(basis, &P1d); CeedChk(ierr); 483 ierr = CeedBasisGetNumNodes(basis, &elemsize); CeedChk(ierr); 484 ierr = CeedBasisGetNumQuadraturePoints1D(basis, &Q1d); CeedChk(ierr); 485 ierr = CeedBasisGetNumQuadraturePoints(basis, &nqpts); CeedChk(ierr); 486 ierr = CeedBasisGetDimension(basis, &dim); CeedChk(ierr); 487 ierr = CeedBasisGetNumComponents(basis, &ncomp); CeedChk(ierr); 488 ierr = CeedElemRestrictionGetNumElements(rstr, &nelem); CeedChk(ierr); 489 ierr = CeedElemRestrictionGetNumNodes(rstr, &nnodes); CeedChk(ierr); 490 491 // Build and diagonalize 1D Mass and Laplacian 492 if (!basis->tensorbasis) 493 return CeedError(ceed, 1, "FDMElementInverse only supported for tensor " 494 "bases"); 495 CeedScalar *work, *mass, *laplace, *x, *x2, *lambda; 496 ierr = CeedMalloc(Q1d*P1d, &work); CeedChk(ierr); 497 ierr = CeedMalloc(P1d*P1d, &mass); CeedChk(ierr); 498 ierr = CeedMalloc(P1d*P1d, &laplace); CeedChk(ierr); 499 ierr = CeedMalloc(P1d*P1d, &x); CeedChk(ierr); 500 ierr = CeedMalloc(P1d*P1d, &x2); CeedChk(ierr); 501 ierr = CeedMalloc(P1d, &lambda); CeedChk(ierr); 502 // -- Mass 503 for (CeedInt i=0; i<Q1d; i++) 504 for (CeedInt j=0; j<P1d; j++) 505 work[i+j*Q1d] = basis->interp1d[i*P1d+j]*basis->qweight1d[i]; 506 ierr = CeedMatrixMultiply(ceed, work, basis->interp1d, mass, P1d, P1d, Q1d); 507 CeedChk(ierr); 508 // -- Laplacian 509 for (CeedInt i=0; i<Q1d; i++) 510 for (CeedInt j=0; j<P1d; j++) 511 work[i+j*Q1d] = basis->grad1d[i*P1d+j]*basis->qweight1d[i]; 512 ierr = CeedMatrixMultiply(ceed, work, basis->grad1d, laplace, P1d, P1d, Q1d); 513 CeedChk(ierr); 514 // -- Diagonalize 515 ierr = CeedSimultaneousDiagonalization(ceed, laplace, mass, x, lambda, P1d); 516 CeedChk(ierr); 517 ierr = CeedFree(&work); CeedChk(ierr); 518 ierr = CeedFree(&mass); CeedChk(ierr); 519 ierr = CeedFree(&laplace); CeedChk(ierr); 520 for (CeedInt i=0; i<P1d; i++) 521 for (CeedInt j=0; j<P1d; j++) 522 x2[i+j*P1d] = x[j+i*P1d]; 523 ierr = CeedFree(&x); CeedChk(ierr); 524 525 // Assemble QFunction 526 CeedVector assembled; 527 CeedElemRestriction rstr_qf; 528 ierr = CeedOperatorAssembleLinearQFunction(op, &assembled, &rstr_qf, 529 request); CeedChk(ierr); 530 ierr = CeedElemRestrictionDestroy(&rstr_qf); CeedChk(ierr); 531 532 // Calculate element averages 533 CeedInt nfields = ((interp?1:0) + (grad?dim:0))*((interp?1:0) + (grad?dim:0)); 534 CeedScalar *elemavg; 535 const CeedScalar *assembledarray, *qweightsarray; 536 CeedVector qweights; 537 ierr = CeedVectorCreate(ceed, nqpts, &qweights); CeedChk(ierr); 538 ierr = CeedBasisApply(basis, 1, CEED_NOTRANSPOSE, CEED_EVAL_WEIGHT, 539 CEED_VECTOR_NONE, qweights); CeedChk(ierr); 540 ierr = CeedVectorGetArrayRead(assembled, CEED_MEM_HOST, &assembledarray); 541 CeedChk(ierr); 542 ierr = CeedVectorGetArrayRead(qweights, CEED_MEM_HOST, &qweightsarray); 543 CeedChk(ierr); 544 ierr = CeedCalloc(nelem, &elemavg); CeedChk(ierr); 545 for (CeedInt e=0; e<nelem; e++) { 546 CeedInt count = 0; 547 for (CeedInt q=0; q<nqpts; q++) 548 for (CeedInt i=0; i<ncomp*ncomp*nfields; i++) 549 if (fabs(assembledarray[e*nelem*nqpts*ncomp*ncomp*nfields + 550 i*nqpts + q]) > CEED_EPSILON) { 551 elemavg[e] += assembledarray[e*nelem*nqpts*ncomp*ncomp*nfields + 552 i*nqpts + q] / qweightsarray[q]; 553 count++; 554 } 555 if (count) 556 elemavg[e] /= count; 557 } 558 ierr = CeedVectorRestoreArrayRead(assembled, &assembledarray); CeedChk(ierr); 559 ierr = CeedVectorDestroy(&assembled); CeedChk(ierr); 560 ierr = CeedVectorRestoreArrayRead(qweights, &qweightsarray); CeedChk(ierr); 561 ierr = CeedVectorDestroy(&qweights); CeedChk(ierr); 562 563 // Build FDM diagonal 564 CeedVector qdata; 565 CeedScalar *qdataarray; 566 ierr = CeedVectorCreate(ceed, nelem*ncomp*nnodes, &qdata); CeedChk(ierr); 567 ierr = CeedVectorSetArray(qdata, CEED_MEM_HOST, CEED_COPY_VALUES, NULL); 568 CeedChk(ierr); 569 ierr = CeedVectorGetArray(qdata, CEED_MEM_HOST, &qdataarray); CeedChk(ierr); 570 for (CeedInt e=0; e<nelem; e++) 571 for (CeedInt c=0; c<ncomp; c++) 572 for (CeedInt n=0; n<nnodes; n++) { 573 if (interp) 574 qdataarray[(e*ncomp+c)*nnodes+n] = 1; 575 if (grad) 576 for (CeedInt d=0; d<dim; d++) { 577 CeedInt i = (n / CeedIntPow(P1d, d)) % P1d; 578 qdataarray[(e*ncomp+c)*nnodes+n] += lambda[i]; 579 } 580 qdataarray[(e*ncomp+c)*nnodes+n] = 1 / (elemavg[e] * 581 qdataarray[(e*ncomp+c)*nnodes+n]); 582 } 583 ierr = CeedFree(&elemavg); CeedChk(ierr); 584 ierr = CeedVectorRestoreArray(qdata, &qdataarray); CeedChk(ierr); 585 586 // Setup FDM operator 587 // -- Basis 588 CeedBasis fdm_basis; 589 CeedScalar *graddummy, *qrefdummy, *qweightdummy; 590 ierr = CeedCalloc(P1d*P1d, &graddummy); CeedChk(ierr); 591 ierr = CeedCalloc(P1d, &qrefdummy); CeedChk(ierr); 592 ierr = CeedCalloc(P1d, &qweightdummy); CeedChk(ierr); 593 ierr = CeedBasisCreateTensorH1(ceed, dim, ncomp, P1d, P1d, x2, graddummy, 594 qrefdummy, qweightdummy, &fdm_basis); 595 CeedChk(ierr); 596 ierr = CeedFree(&graddummy); CeedChk(ierr); 597 ierr = CeedFree(&qrefdummy); CeedChk(ierr); 598 ierr = CeedFree(&qweightdummy); CeedChk(ierr); 599 ierr = CeedFree(&x2); CeedChk(ierr); 600 ierr = CeedFree(&lambda); CeedChk(ierr); 601 602 // -- Restriction 603 CeedElemRestriction rstr_i; 604 ierr = CeedElemRestrictionCreateIdentity(ceed, nelem, nnodes, nnodes*nelem, 605 ncomp, &rstr_i); CeedChk(ierr); 606 // -- QFunction 607 CeedQFunction mass_qf; 608 ierr = CeedQFunctionCreateInteriorByName(ceed, "MassApply", &mass_qf); 609 CeedChk(ierr); 610 // -- Operator 611 ierr = CeedOperatorCreate(ceed, mass_qf, NULL, NULL, fdminv); CeedChk(ierr); 612 CeedOperatorSetField(*fdminv, "u", rstr_i, CEED_NOTRANSPOSE, 613 fdm_basis, CEED_VECTOR_ACTIVE); CeedChk(ierr); 614 CeedOperatorSetField(*fdminv, "qdata", rstr_i, CEED_NOTRANSPOSE, 615 CEED_BASIS_COLLOCATED, qdata); CeedChk(ierr); 616 CeedOperatorSetField(*fdminv, "v", rstr_i, CEED_NOTRANSPOSE, 617 fdm_basis, CEED_VECTOR_ACTIVE); CeedChk(ierr); 618 619 // Cleanup 620 ierr = CeedVectorDestroy(&qdata); CeedChk(ierr); 621 ierr = CeedBasisDestroy(&fdm_basis); CeedChk(ierr); 622 ierr = CeedElemRestrictionDestroy(&rstr_i); CeedChk(ierr); 623 ierr = CeedQFunctionDestroy(&mass_qf); CeedChk(ierr); 624 625 return 0; 626 } 627 628 629 /** 630 @brief Apply CeedOperator to a vector 631 632 This computes the action of the operator on the specified (active) input, 633 yielding its (active) output. All inputs and outputs must be specified using 634 CeedOperatorSetField(). 635 636 @param op CeedOperator to apply 637 @param[in] in CeedVector containing input state or CEED_VECTOR_NONE if 638 there are no active inputs 639 @param[out] out CeedVector to store result of applying operator (must be 640 distinct from @a in) or CEED_VECTOR_NONE if there are no 641 active outputs 642 @param request Address of CeedRequest for non-blocking completion, else 643 CEED_REQUEST_IMMEDIATE 644 645 @return An error code: 0 - success, otherwise - failure 646 647 @ref Basic 648 **/ 649 int CeedOperatorApply(CeedOperator op, CeedVector in, CeedVector out, 650 CeedRequest *request) { 651 int ierr; 652 Ceed ceed = op->ceed; 653 ierr = CeedOperatorCheckReady(ceed, op); CeedChk(ierr); 654 655 if (op->numelements) { 656 // Standard Operator 657 if (op->Apply) { 658 ierr = op->Apply(op, in, out, request); CeedChk(ierr); 659 } else { 660 // Zero all output vectors 661 CeedQFunction qf = op->qf; 662 for (CeedInt i=0; i<qf->numoutputfields; i++) { 663 CeedVector vec = op->outputfields[i]->vec; 664 if (vec == CEED_VECTOR_ACTIVE) 665 vec = out; 666 if (vec != CEED_VECTOR_NONE) { 667 ierr = CeedVectorSetValue(vec, 0.0); CeedChk(ierr); 668 } 669 } 670 // Apply 671 ierr = op->ApplyAdd(op, in, out, request); CeedChk(ierr); 672 } 673 } else if (op->composite) { 674 // Composite Operator 675 if (op->ApplyComposite) { 676 ierr = op->ApplyComposite(op, in, out, request); CeedChk(ierr); 677 } else { 678 CeedInt numsub; 679 ierr = CeedOperatorGetNumSub(op, &numsub); CeedChk(ierr); 680 CeedOperator *suboperators; 681 ierr = CeedOperatorGetSubList(op, &suboperators); CeedChk(ierr); 682 683 // Zero all output vectors 684 if (out != CEED_VECTOR_NONE) { 685 ierr = CeedVectorSetValue(out, 0.0); CeedChk(ierr); 686 } 687 for (CeedInt i=0; i<numsub; i++) { 688 for (CeedInt j=0; j<suboperators[i]->qf->numoutputfields; j++) { 689 CeedVector vec = suboperators[i]->outputfields[j]->vec; 690 if (vec != CEED_VECTOR_ACTIVE && vec != CEED_VECTOR_NONE) { 691 ierr = CeedVectorSetValue(vec, 0.0); CeedChk(ierr); 692 } 693 } 694 } 695 // Apply 696 for (CeedInt i=0; i<op->numsub; i++) { 697 ierr = CeedOperatorApplyAdd(op->suboperators[i], in, out, request); 698 CeedChk(ierr); 699 } 700 } 701 } 702 703 return 0; 704 } 705 706 /** 707 @brief Apply CeedOperator to a vector and add result to output vector 708 709 This computes the action of the operator on the specified (active) input, 710 yielding its (active) output. All inputs and outputs must be specified using 711 CeedOperatorSetField(). 712 713 @param op CeedOperator to apply 714 @param[in] in CeedVector containing input state or NULL if there are no 715 active inputs 716 @param[out] out CeedVector to sum in result of applying operator (must be 717 distinct from @a in) or NULL if there are no active outputs 718 @param request Address of CeedRequest for non-blocking completion, else 719 CEED_REQUEST_IMMEDIATE 720 721 @return An error code: 0 - success, otherwise - failure 722 723 @ref Basic 724 **/ 725 int CeedOperatorApplyAdd(CeedOperator op, CeedVector in, CeedVector out, 726 CeedRequest *request) { 727 int ierr; 728 Ceed ceed = op->ceed; 729 ierr = CeedOperatorCheckReady(ceed, op); CeedChk(ierr); 730 731 if (op->numelements) { 732 // Standard Operator 733 ierr = op->ApplyAdd(op, in, out, request); CeedChk(ierr); 734 } else if (op->composite) { 735 // Composite Operator 736 if (op->ApplyAddComposite) { 737 ierr = op->ApplyAddComposite(op, in, out, request); CeedChk(ierr); 738 } else { 739 CeedInt numsub; 740 ierr = CeedOperatorGetNumSub(op, &numsub); CeedChk(ierr); 741 CeedOperator *suboperators; 742 ierr = CeedOperatorGetSubList(op, &suboperators); CeedChk(ierr); 743 744 for (CeedInt i=0; i<numsub; i++) { 745 ierr = CeedOperatorApplyAdd(suboperators[i], in, out, request); 746 CeedChk(ierr); 747 } 748 } 749 } 750 751 return 0; 752 } 753 754 /** 755 @brief Get the Ceed associated with a CeedOperator 756 757 @param op CeedOperator 758 @param[out] ceed Variable to store Ceed 759 760 @return An error code: 0 - success, otherwise - failure 761 762 @ref Advanced 763 **/ 764 765 int CeedOperatorGetCeed(CeedOperator op, Ceed *ceed) { 766 *ceed = op->ceed; 767 return 0; 768 } 769 770 /** 771 @brief Get the number of elements associated with a CeedOperator 772 773 @param op CeedOperator 774 @param[out] numelem Variable to store number of elements 775 776 @return An error code: 0 - success, otherwise - failure 777 778 @ref Advanced 779 **/ 780 781 int CeedOperatorGetNumElements(CeedOperator op, CeedInt *numelem) { 782 if (op->composite) 783 // LCOV_EXCL_START 784 return CeedError(op->ceed, 1, "Not defined for composite operator"); 785 // LCOV_EXCL_STOP 786 787 *numelem = op->numelements; 788 return 0; 789 } 790 791 /** 792 @brief Get the number of quadrature points associated with a CeedOperator 793 794 @param op CeedOperator 795 @param[out] numqpts Variable to store vector number of quadrature points 796 797 @return An error code: 0 - success, otherwise - failure 798 799 @ref Advanced 800 **/ 801 802 int CeedOperatorGetNumQuadraturePoints(CeedOperator op, CeedInt *numqpts) { 803 if (op->composite) 804 // LCOV_EXCL_START 805 return CeedError(op->ceed, 1, "Not defined for composite operator"); 806 // LCOV_EXCL_STOP 807 808 *numqpts = op->numqpoints; 809 return 0; 810 } 811 812 /** 813 @brief Get the number of arguments associated with a CeedOperator 814 815 @param op CeedOperator 816 @param[out] numargs Variable to store vector number of arguments 817 818 @return An error code: 0 - success, otherwise - failure 819 820 @ref Advanced 821 **/ 822 823 int CeedOperatorGetNumArgs(CeedOperator op, CeedInt *numargs) { 824 if (op->composite) 825 // LCOV_EXCL_START 826 return CeedError(op->ceed, 1, "Not defined for composite operators"); 827 // LCOV_EXCL_STOP 828 829 *numargs = op->nfields; 830 return 0; 831 } 832 833 /** 834 @brief Get the setup status of a CeedOperator 835 836 @param op CeedOperator 837 @param[out] setupdone Variable to store setup status 838 839 @return An error code: 0 - success, otherwise - failure 840 841 @ref Advanced 842 **/ 843 844 int CeedOperatorGetSetupStatus(CeedOperator op, bool *setupdone) { 845 *setupdone = op->setupdone; 846 return 0; 847 } 848 849 /** 850 @brief Get the QFunction associated with a CeedOperator 851 852 @param op CeedOperator 853 @param[out] qf Variable to store QFunction 854 855 @return An error code: 0 - success, otherwise - failure 856 857 @ref Advanced 858 **/ 859 860 int CeedOperatorGetQFunction(CeedOperator op, CeedQFunction *qf) { 861 if (op->composite) 862 // LCOV_EXCL_START 863 return CeedError(op->ceed, 1, "Not defined for composite operator"); 864 // LCOV_EXCL_STOP 865 866 *qf = op->qf; 867 return 0; 868 } 869 870 /** 871 @brief Get the number of suboperators associated with a CeedOperator 872 873 @param op CeedOperator 874 @param[out] numsub Variable to store number of suboperators 875 876 @return An error code: 0 - success, otherwise - failure 877 878 @ref Advanced 879 **/ 880 881 int CeedOperatorGetNumSub(CeedOperator op, CeedInt *numsub) { 882 if (!op->composite) 883 // LCOV_EXCL_START 884 return CeedError(op->ceed, 1, "Not a composite operator"); 885 // LCOV_EXCL_STOP 886 887 *numsub = op->numsub; 888 return 0; 889 } 890 891 /** 892 @brief Get the list of suboperators associated with a CeedOperator 893 894 @param op CeedOperator 895 @param[out] suboperators Variable to store list of suboperators 896 897 @return An error code: 0 - success, otherwise - failure 898 899 @ref Advanced 900 **/ 901 902 int CeedOperatorGetSubList(CeedOperator op, CeedOperator **suboperators) { 903 if (!op->composite) 904 // LCOV_EXCL_START 905 return CeedError(op->ceed, 1, "Not a composite operator"); 906 // LCOV_EXCL_STOP 907 908 *suboperators = op->suboperators; 909 return 0; 910 } 911 912 /** 913 @brief Set the backend data of a CeedOperator 914 915 @param[out] op CeedOperator 916 @param data Data to set 917 918 @return An error code: 0 - success, otherwise - failure 919 920 @ref Advanced 921 **/ 922 923 int CeedOperatorSetData(CeedOperator op, void **data) { 924 op->data = *data; 925 return 0; 926 } 927 928 /** 929 @brief Get the backend data of a CeedOperator 930 931 @param op CeedOperator 932 @param[out] data Variable to store data 933 934 @return An error code: 0 - success, otherwise - failure 935 936 @ref Advanced 937 **/ 938 939 int CeedOperatorGetData(CeedOperator op, void **data) { 940 *data = op->data; 941 return 0; 942 } 943 944 /** 945 @brief Set the setup flag of a CeedOperator to True 946 947 @param op CeedOperator 948 949 @return An error code: 0 - success, otherwise - failure 950 951 @ref Advanced 952 **/ 953 954 int CeedOperatorSetSetupDone(CeedOperator op) { 955 op->setupdone = 1; 956 return 0; 957 } 958 959 /** 960 @brief View a field of a CeedOperator 961 962 @param[in] field Operator field to view 963 @param[in] fieldnumber Number of field being viewed 964 @param[in] stream Stream to view to, e.g., stdout 965 966 @return An error code: 0 - success, otherwise - failure 967 968 @ref Utility 969 **/ 970 static int CeedOperatorFieldView(CeedOperatorField field, 971 CeedQFunctionField qffield, 972 CeedInt fieldnumber, bool sub, bool in, 973 FILE *stream) { 974 const char *pre = sub ? " " : ""; 975 const char *inout = in ? "Input" : "Output"; 976 977 fprintf(stream, "%s %s Field [%d]:\n" 978 "%s Name: \"%s\"\n" 979 "%s Lmode: \"%s\"\n", 980 pre, inout, fieldnumber, pre, qffield->fieldname, 981 pre, CeedTransposeModes[field->lmode]); 982 983 if (field->basis == CEED_BASIS_COLLOCATED) 984 fprintf(stream, "%s Collocated basis\n", pre); 985 986 if (field->vec == CEED_VECTOR_ACTIVE) 987 fprintf(stream, "%s Active vector\n", pre); 988 else if (field->vec == CEED_VECTOR_NONE) 989 fprintf(stream, "%s No vector\n", pre); 990 991 return 0; 992 } 993 994 /** 995 @brief View a single CeedOperator 996 997 @param[in] op CeedOperator to view 998 @param[in] stream Stream to write; typically stdout/stderr or a file 999 1000 @return Error code: 0 - success, otherwise - failure 1001 1002 @ref Utility 1003 **/ 1004 int CeedOperatorSingleView(CeedOperator op, bool sub, FILE *stream) { 1005 int ierr; 1006 const char *pre = sub ? " " : ""; 1007 1008 CeedInt totalfields; 1009 ierr = CeedOperatorGetNumArgs(op, &totalfields); CeedChk(ierr); 1010 1011 fprintf(stream, "%s %d Field%s\n", pre, totalfields, totalfields>1 ? "s" : ""); 1012 1013 fprintf(stream, "%s %d Input Field%s:\n", pre, op->qf->numinputfields, 1014 op->qf->numinputfields>1 ? "s" : ""); 1015 for (CeedInt i=0; i<op->qf->numinputfields; i++) { 1016 ierr = CeedOperatorFieldView(op->inputfields[i], op->qf->inputfields[i], 1017 i, sub, 1, stream); CeedChk(ierr); 1018 } 1019 1020 fprintf(stream, "%s %d Output Field%s:\n", pre, op->qf->numoutputfields, 1021 op->qf->numoutputfields>1 ? "s" : ""); 1022 for (CeedInt i=0; i<op->qf->numoutputfields; i++) { 1023 ierr = CeedOperatorFieldView(op->outputfields[i], op->qf->inputfields[i], 1024 i, sub, 0, stream); CeedChk(ierr); 1025 } 1026 1027 return 0; 1028 } 1029 1030 /** 1031 @brief View a CeedOperator 1032 1033 @param[in] op CeedOperator to view 1034 @param[in] stream Stream to write; typically stdout/stderr or a file 1035 1036 @return Error code: 0 - success, otherwise - failure 1037 1038 @ref Utility 1039 **/ 1040 int CeedOperatorView(CeedOperator op, FILE *stream) { 1041 int ierr; 1042 1043 if (op->composite) { 1044 fprintf(stream, "Composite CeedOperator\n"); 1045 1046 for (CeedInt i=0; i<op->numsub; i++) { 1047 fprintf(stream, " SubOperator [%d]:\n", i); 1048 ierr = CeedOperatorSingleView(op->suboperators[i], 1, stream); 1049 CeedChk(ierr); 1050 } 1051 } else { 1052 fprintf(stream, "CeedOperator\n"); 1053 ierr = CeedOperatorSingleView(op, 0, stream); CeedChk(ierr); 1054 } 1055 1056 return 0; 1057 } 1058 1059 /** 1060 @brief Get the CeedOperatorFields of a CeedOperator 1061 1062 @param op CeedOperator 1063 @param[out] inputfields Variable to store inputfields 1064 @param[out] outputfields Variable to store outputfields 1065 1066 @return An error code: 0 - success, otherwise - failure 1067 1068 @ref Advanced 1069 **/ 1070 1071 int CeedOperatorGetFields(CeedOperator op, CeedOperatorField **inputfields, 1072 CeedOperatorField **outputfields) { 1073 if (op->composite) 1074 // LCOV_EXCL_START 1075 return CeedError(op->ceed, 1, "Not defined for composite operator"); 1076 // LCOV_EXCL_STOP 1077 1078 if (inputfields) *inputfields = op->inputfields; 1079 if (outputfields) *outputfields = op->outputfields; 1080 return 0; 1081 } 1082 1083 /** 1084 @brief Get the L vector CeedTransposeMode of a CeedOperatorField 1085 1086 @param opfield CeedOperatorField 1087 @param[out] lmode Variable to store CeedTransposeMode 1088 1089 @return An error code: 0 - success, otherwise - failure 1090 1091 @ref Advanced 1092 **/ 1093 1094 int CeedOperatorFieldGetLMode(CeedOperatorField opfield, 1095 CeedTransposeMode *lmode) { 1096 *lmode = opfield->lmode; 1097 return 0; 1098 } 1099 1100 /** 1101 @brief Get the CeedElemRestriction of a CeedOperatorField 1102 1103 @param opfield CeedOperatorField 1104 @param[out] rstr Variable to store CeedElemRestriction 1105 1106 @return An error code: 0 - success, otherwise - failure 1107 1108 @ref Advanced 1109 **/ 1110 1111 int CeedOperatorFieldGetElemRestriction(CeedOperatorField opfield, 1112 CeedElemRestriction *rstr) { 1113 *rstr = opfield->Erestrict; 1114 return 0; 1115 } 1116 1117 /** 1118 @brief Get the CeedBasis of a CeedOperatorField 1119 1120 @param opfield CeedOperatorField 1121 @param[out] basis Variable to store CeedBasis 1122 1123 @return An error code: 0 - success, otherwise - failure 1124 1125 @ref Advanced 1126 **/ 1127 1128 int CeedOperatorFieldGetBasis(CeedOperatorField opfield, CeedBasis *basis) { 1129 *basis = opfield->basis; 1130 return 0; 1131 } 1132 1133 /** 1134 @brief Get the CeedVector of a CeedOperatorField 1135 1136 @param opfield CeedOperatorField 1137 @param[out] vec Variable to store CeedVector 1138 1139 @return An error code: 0 - success, otherwise - failure 1140 1141 @ref Advanced 1142 **/ 1143 1144 int CeedOperatorFieldGetVector(CeedOperatorField opfield, CeedVector *vec) { 1145 *vec = opfield->vec; 1146 return 0; 1147 } 1148 1149 /** 1150 @brief Destroy a CeedOperator 1151 1152 @param op CeedOperator to destroy 1153 1154 @return An error code: 0 - success, otherwise - failure 1155 1156 @ref Basic 1157 **/ 1158 int CeedOperatorDestroy(CeedOperator *op) { 1159 int ierr; 1160 1161 if (!*op || --(*op)->refcount > 0) return 0; 1162 if ((*op)->Destroy) { 1163 ierr = (*op)->Destroy(*op); CeedChk(ierr); 1164 } 1165 ierr = CeedDestroy(&(*op)->ceed); CeedChk(ierr); 1166 // Free fields 1167 for (int i=0; i<(*op)->nfields; i++) 1168 if ((*op)->inputfields[i]) { 1169 ierr = CeedElemRestrictionDestroy(&(*op)->inputfields[i]->Erestrict); 1170 CeedChk(ierr); 1171 if ((*op)->inputfields[i]->basis != CEED_BASIS_COLLOCATED) { 1172 ierr = CeedBasisDestroy(&(*op)->inputfields[i]->basis); CeedChk(ierr); 1173 } 1174 if ((*op)->inputfields[i]->vec != CEED_VECTOR_ACTIVE && 1175 (*op)->inputfields[i]->vec != CEED_VECTOR_NONE ) { 1176 ierr = CeedVectorDestroy(&(*op)->inputfields[i]->vec); CeedChk(ierr); 1177 } 1178 ierr = CeedFree(&(*op)->inputfields[i]); CeedChk(ierr); 1179 } 1180 for (int i=0; i<(*op)->nfields; i++) 1181 if ((*op)->outputfields[i]) { 1182 ierr = CeedElemRestrictionDestroy(&(*op)->outputfields[i]->Erestrict); 1183 CeedChk(ierr); 1184 if ((*op)->outputfields[i]->basis != CEED_BASIS_COLLOCATED) { 1185 ierr = CeedBasisDestroy(&(*op)->outputfields[i]->basis); CeedChk(ierr); 1186 } 1187 if ((*op)->outputfields[i]->vec != CEED_VECTOR_ACTIVE && 1188 (*op)->outputfields[i]->vec != CEED_VECTOR_NONE ) { 1189 ierr = CeedVectorDestroy(&(*op)->outputfields[i]->vec); CeedChk(ierr); 1190 } 1191 ierr = CeedFree(&(*op)->outputfields[i]); CeedChk(ierr); 1192 } 1193 // Destroy suboperators 1194 for (int i=0; i<(*op)->numsub; i++) 1195 if ((*op)->suboperators[i]) { 1196 ierr = CeedOperatorDestroy(&(*op)->suboperators[i]); CeedChk(ierr); 1197 } 1198 ierr = CeedQFunctionDestroy(&(*op)->qf); CeedChk(ierr); 1199 ierr = CeedQFunctionDestroy(&(*op)->dqf); CeedChk(ierr); 1200 ierr = CeedQFunctionDestroy(&(*op)->dqfT); CeedChk(ierr); 1201 1202 // Destroy fallback 1203 if ((*op)->opfallback) { 1204 ierr = (*op)->qffallback->Destroy((*op)->qffallback); CeedChk(ierr); 1205 ierr = CeedFree(&(*op)->qffallback); CeedChk(ierr); 1206 ierr = (*op)->opfallback->Destroy((*op)->opfallback); CeedChk(ierr); 1207 ierr = CeedFree(&(*op)->opfallback); CeedChk(ierr); 1208 } 1209 1210 ierr = CeedFree(&(*op)->inputfields); CeedChk(ierr); 1211 ierr = CeedFree(&(*op)->outputfields); CeedChk(ierr); 1212 ierr = CeedFree(&(*op)->suboperators); CeedChk(ierr); 1213 ierr = CeedFree(op); CeedChk(ierr); 1214 return 0; 1215 } 1216 1217 /// @} 1218