LCOV - code coverage report
Current view: top level - fem - coefficient.hpp (source / functions) Coverage Total Hit
Test: Palace Coverage Report Lines: 49.0 % 255 125
Test Date: 2025-10-23 22:45:05 Functions: 35.4 % 65 23
Legend: Lines: hit not hit

            Line data    Source code
       1              : // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
       2              : // SPDX-License-Identifier: Apache-2.0
       3              : 
       4              : #ifndef PALACE_FEM_COEFFICIENT_HPP
       5              : #define PALACE_FEM_COEFFICIENT_HPP
       6              : 
       7              : #include <complex>
       8              : #include <memory>
       9              : #include <utility>
      10              : #include <vector>
      11              : #include <mfem.hpp>
      12              : #include "fem/gridfunction.hpp"
      13              : #include "linalg/vector.hpp"
      14              : #include "models/materialoperator.hpp"
      15              : #include "utils/geodata.hpp"
      16              : #include "utils/labels.hpp"
      17              : 
      18              : // XX TODO: Add bulk element Eval() overrides to speed up postprocessing (also needed in
      19              : //          mfem::DataCollection classes.
      20              : 
      21              : namespace palace
      22              : {
      23              : 
      24              : //
      25              : // Derived coefficients which compute single values on internal boundaries where a possibly
      26              : // discontinuous function is given as an input grid function. These are all cheap to
      27              : // construct by design. All methods assume the provided grid function is ready for parallel
      28              : // comm on shared faces after a call to ExchangeFaceNbrData.
      29              : //
      30              : 
      31              : // Base class for coefficients which need to evaluate a GridFunction in a domain element
      32              : // attached to a boundary element, or both domain elements on either side for internal
      33              : // boundaries.
      34              : class BdrGridFunctionCoefficient
      35              : {
      36              : protected:
      37              :   // XX TODO: For thread-safety (multiple threads evaluating a coefficient simultaneously),
      38              :   //          the FET, FET.Elem1, and FET.Elem2 objects cannot be shared.
      39              :   const mfem::ParMesh &mesh;
      40              :   mfem::FaceElementTransformations FET;
      41              :   mfem::IsoparametricTransformation T1, T2;
      42              : 
      43              :   bool GetBdrElementNeighborTransformations(int i, const mfem::IntegrationPoint &ip)
      44              :   {
      45              :     // Get the element transformations neighboring the element, and optionally set the
      46              :     // integration point too.
      47        24300 :     return GetBdrElementNeighborTransformations(i, mesh, FET, T1, T2, &ip);
      48              :   }
      49              : 
      50              : public:
      51          120 :   BdrGridFunctionCoefficient(const mfem::ParMesh &mesh) : mesh(mesh) {}
      52              : 
      53              :   // For a boundary element, return the element transformation objects for the neighboring
      54              :   // domain elements. FET.Elem2 may be nullptr if the boundary is a true one-sided boundary,
      55              :   // but if it is shared with another subdomain then it will be populated. Expects
      56              :   // ParMesh::ExchangeFaceNbrData has been called already.
      57              :   static bool GetBdrElementNeighborTransformations(
      58              :       int i, const mfem::ParMesh &mesh, mfem::FaceElementTransformations &FET,
      59              :       mfem::IsoparametricTransformation &T1, mfem::IsoparametricTransformation &T2,
      60              :       const mfem::IntegrationPoint *ip = nullptr);
      61              : 
      62              :   // Return normal vector to the boundary element at an integration point. For a face
      63              :   // element, the normal points out of the element (from element 1 into element 2, if it
      64              :   // exists). This convention can be flipped with the optional parameter. It is assumed
      65              :   // that the element transformation has already been configured at the integration point
      66              :   // of interest.
      67        36900 :   static void GetNormal(mfem::ElementTransformation &T, mfem::Vector &normal,
      68              :                         bool invert = false)
      69              :   {
      70              :     MFEM_ASSERT(normal.Size() == T.GetSpaceDim(),
      71              :                 "Size mismatch for normal vector (space dimension = " << T.GetSpaceDim()
      72              :                                                                       << ")!");
      73        36900 :     mfem::CalcOrtho(T.Jacobian(), normal);
      74        36900 :     normal /= invert ? -normal.Norml2() : normal.Norml2();
      75        36900 :   }
      76              : };
      77              : 
      78              : // Computes surface current Jₛ = n x H = n x μ⁻¹ B on boundaries from B as a vector grid
      79              : // function where n is an inward normal (computes -n x H for outward normal n). For a
      80              : // two-sided internal boundary, the contributions from both sides add.
      81            0 : class BdrSurfaceCurrentVectorCoefficient : public mfem::VectorCoefficient,
      82              :                                            public BdrGridFunctionCoefficient
      83              : {
      84              : private:
      85              :   const mfem::ParGridFunction &B;
      86              :   const MaterialOperator &mat_op;
      87              : 
      88              : public:
      89           27 :   BdrSurfaceCurrentVectorCoefficient(const mfem::ParGridFunction &B,
      90              :                                      const MaterialOperator &mat_op)
      91           27 :     : mfem::VectorCoefficient(B.VectorDim()),
      92           27 :       BdrGridFunctionCoefficient(*B.ParFESpace()->GetParMesh()), B(B), mat_op(mat_op)
      93              :   {
      94           27 :   }
      95              : 
      96              :   using mfem::VectorCoefficient::Eval;
      97         4212 :   void Eval(mfem::Vector &V, mfem::ElementTransformation &T,
      98              :             const mfem::IntegrationPoint &ip) override
      99              :   {
     100              :     // Get neighboring elements.
     101              :     MFEM_ASSERT(T.ElementType == mfem::ElementTransformation::BDR_ELEMENT,
     102              :                 "Unexpected element type in BdrSurfaceCurrentVectorCoefficient!");
     103         4212 :     bool ori = GetBdrElementNeighborTransformations(T.ElementNo, ip);
     104              : 
     105              :     // For interior faces, compute Jₛ = n x H = n x μ⁻¹ (B1 - B2), where B1 (B2) is B in
     106              :     // element 1 (element 2) and n points into element 1.
     107              :     double W_data[3], VU_data[3];
     108         8424 :     mfem::Vector W(W_data, vdim), VU(VU_data, vdim);
     109         4212 :     B.GetVectorValue(*FET.Elem1, FET.Elem1->GetIntPoint(), W);
     110         4212 :     mat_op.GetInvPermeability(FET.Elem1->Attribute).Mult(W, VU);
     111         4212 :     if (FET.Elem2)
     112              :     {
     113              :       // Double-sided, not a true boundary. Add result with opposite normal.
     114              :       double VL_data[3];
     115            0 :       mfem::Vector VL(VL_data, vdim);
     116            0 :       B.GetVectorValue(*FET.Elem2, FET.Elem2->GetIntPoint(), W);
     117            0 :       mat_op.GetInvPermeability(FET.Elem2->Attribute).Mult(W, VL);
     118            0 :       VU -= VL;
     119              :     }
     120              : 
     121              :     // Orient with normal pointing into element 1.
     122              :     double normal_data[3];
     123         4212 :     mfem::Vector normal(normal_data, vdim);
     124         4212 :     GetNormal(T, normal, ori);
     125         4212 :     V.SetSize(vdim);
     126         4212 :     linalg::Cross3(normal, VU, V);
     127         4212 :   }
     128              : };
     129              : 
     130              : // Computes the flux Φₛ = F ⋅ n with F = B or ε D on interior boundary elements using B or
     131              : // E given as a vector grid function. For a two-sided internal boundary, the contributions
     132              : // from both sides can either add or be averaged.
     133              : template <SurfaceFlux Type>
     134            0 : class BdrSurfaceFluxCoefficient : public mfem::Coefficient,
     135              :                                   public BdrGridFunctionCoefficient
     136              : {
     137              : private:
     138              :   const mfem::ParGridFunction *E, *B;
     139              :   const MaterialOperator &mat_op;
     140              :   bool two_sided;
     141              :   const mfem::Vector &x0;
     142              : 
     143              :   void GetLocalFlux(mfem::ElementTransformation &T, mfem::Vector &V) const;
     144              : 
     145              : public:
     146           18 :   BdrSurfaceFluxCoefficient(const mfem::ParGridFunction *E, const mfem::ParGridFunction *B,
     147              :                             const MaterialOperator &mat_op, bool two_sided,
     148              :                             const mfem::Vector &x0)
     149              :     : mfem::Coefficient(), BdrGridFunctionCoefficient(E ? *E->ParFESpace()->GetParMesh()
     150              :                                                         : *B->ParFESpace()->GetParMesh()),
     151           36 :       E(E), B(B), mat_op(mat_op), two_sided(two_sided), x0(x0)
     152              :   {
     153           18 :     MFEM_VERIFY((E || (Type != SurfaceFlux::ELECTRIC && Type != SurfaceFlux::POWER)) &&
     154              :                     (B || (Type != SurfaceFlux::MAGNETIC && Type != SurfaceFlux::POWER)),
     155              :                 "Missing E or B field grid function for surface flux coefficient!");
     156           18 :   }
     157              : 
     158         3888 :   double Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override
     159              :   {
     160              :     // Get neighboring elements.
     161              :     MFEM_ASSERT(T.ElementType == mfem::ElementTransformation::BDR_ELEMENT,
     162              :                 "Unexpected element type in BdrSurfaceFluxCoefficient!");
     163         3888 :     bool ori = GetBdrElementNeighborTransformations(T.ElementNo, ip);
     164              : 
     165              :     // For interior faces, compute either F ⋅ n as the average or by adding the
     166              :     // contributions from opposite sides with opposite normals.
     167         3888 :     const int vdim = T.GetSpaceDim();
     168              :     double VU_data[3];
     169              :     mfem::Vector VU(VU_data, vdim);
     170         3888 :     GetLocalFlux(*FET.Elem1, VU);
     171         3888 :     if (FET.Elem2)
     172              :     {
     173              :       // Double-sided, not a true boundary.
     174              :       double VL_data[3];
     175              :       mfem::Vector VL(VL_data, vdim);
     176            0 :       GetLocalFlux(*FET.Elem2, VL);
     177            0 :       if (two_sided)
     178              :       {
     179              :         // Add result with opposite normal. This only happens when crack_bdr_elements =
     180              :         // false (two_sided = true doesn't make sense for an internal boundary without an
     181              :         // associated BC).
     182            0 :         VU -= VL;
     183              :       }
     184              :       else
     185              :       {
     186              :         // Take the average of the values on both sides.
     187            0 :         add(0.5, VU, VL, VU);
     188              :       }
     189              :     }
     190              : 
     191              :     // Dot with normal direction and assign appropriate sign. The normal is oriented to
     192              :     // point into element 1.
     193              :     double normal_data[3];
     194              :     mfem::Vector normal(normal_data, vdim);
     195         3888 :     GetNormal(T, normal, ori);
     196         3888 :     double flux = VU * normal;
     197         3888 :     if (two_sided)
     198              :     {
     199              :       return flux;
     200              :     }
     201              :     else
     202              :     {
     203              :       // Orient outward from the surface with the given center.
     204              :       double x_data[3];
     205              :       mfem::Vector x(x_data, vdim);
     206            0 :       T.Transform(ip, x);
     207            0 :       x -= x0;
     208            0 :       return (x * normal < 0.0) ? -flux : flux;
     209              :     }
     210              :   }
     211              : };
     212              : 
     213              : template <>
     214         3888 : inline void BdrSurfaceFluxCoefficient<SurfaceFlux::ELECTRIC>::GetLocalFlux(
     215              :     mfem::ElementTransformation &T, mfem::Vector &V) const
     216              : {
     217              :   // Flux D.
     218              :   double W_data[3];
     219         3888 :   mfem::Vector W(W_data, T.GetSpaceDim());
     220         3888 :   E->GetVectorValue(T, T.GetIntPoint(), W);
     221         3888 :   mat_op.GetPermittivityReal(T.Attribute).Mult(W, V);
     222         3888 : }
     223              : 
     224              : template <>
     225              : inline void BdrSurfaceFluxCoefficient<SurfaceFlux::MAGNETIC>::GetLocalFlux(
     226              :     mfem::ElementTransformation &T, mfem::Vector &V) const
     227              : {
     228              :   // Flux B.
     229            0 :   B->GetVectorValue(T, T.GetIntPoint(), V);
     230            0 : }
     231              : 
     232              : template <>
     233              : inline void
     234            0 : BdrSurfaceFluxCoefficient<SurfaceFlux::POWER>::GetLocalFlux(mfem::ElementTransformation &T,
     235              :                                                             mfem::Vector &V) const
     236              : {
     237              :   // Flux E x H = E x μ⁻¹ B.
     238              :   double W1_data[3], W2_data[3];
     239            0 :   mfem::Vector W1(W1_data, T.GetSpaceDim()), W2(W2_data, T.GetSpaceDim());
     240            0 :   B->GetVectorValue(T, T.GetIntPoint(), W1);
     241            0 :   mat_op.GetInvPermeability(T.Attribute).Mult(W1, W2);
     242            0 :   E->GetVectorValue(T, T.GetIntPoint(), W1);
     243            0 :   V.SetSize(W1.Size());
     244            0 :   linalg::Cross3(W1, W2, V);
     245            0 : }
     246              : 
     247              : // Computes a single-valued α Eᵀ E on boundaries from E given as a vector grid function.
     248              : // Uses the neighbor element on a user specified side to compute a single-sided value for
     249              : // potentially discontinuous solutions for an interior boundary element. The four cases
     250              : // correspond to a generic interface vs. specializations for metal-air, metal-substrate,
     251              : // and substrate-air interfaces following:
     252              : //   J. Wenner et al., Surface loss simulations of superconducting coplanar waveguide
     253              : //     resonators, Appl. Phys. Lett. (2011).
     254              : template <InterfaceDielectric Type>
     255            0 : class InterfaceDielectricCoefficient : public mfem::Coefficient,
     256              :                                        public BdrGridFunctionCoefficient
     257              : {
     258              : private:
     259              :   const GridFunction &E;
     260              :   const MaterialOperator &mat_op;
     261              :   const double t_i, epsilon_i;
     262              : 
     263            0 :   void Initialize(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip,
     264              :                   mfem::Vector *normal)
     265              :   {
     266              :     // Get neighboring elements and the normal vector, oriented to point into element 1.
     267              :     MFEM_ASSERT(T.ElementType == mfem::ElementTransformation::BDR_ELEMENT,
     268              :                 "Unexpected element type in InterfaceDielectricCoefficient!");
     269            0 :     bool ori = GetBdrElementNeighborTransformations(T.ElementNo, ip);
     270            0 :     if (normal)
     271              :     {
     272            0 :       GetNormal(T, *normal, ori);
     273              :     }
     274            0 :   }
     275              : 
     276            0 :   int GetLocalVectorValue(const mfem::ParGridFunction &U, mfem::Vector &V,
     277              :                           bool vacuum_side) const
     278              :   {
     279              :     constexpr double threshold = 1.0 - 1.0e-6;
     280              :     const bool use_elem1 =
     281            0 :         ((vacuum_side && mat_op.GetLightSpeedMax(FET.Elem1->Attribute) >= threshold) ||
     282            0 :          (!vacuum_side && mat_op.GetLightSpeedMax(FET.Elem1->Attribute) < threshold));
     283              :     const bool use_elem2 =
     284            0 :         (FET.Elem2 &&
     285            0 :          ((vacuum_side && mat_op.GetLightSpeedMax(FET.Elem2->Attribute) >= threshold) ||
     286            0 :           (!vacuum_side && mat_op.GetLightSpeedMax(FET.Elem2->Attribute) < threshold)));
     287            0 :     if (use_elem1)
     288              :     {
     289            0 :       U.GetVectorValue(*FET.Elem1, FET.Elem1->GetIntPoint(), V);
     290            0 :       if (use_elem2)
     291              :       {
     292              :         // Double-sided, not a true boundary. Just average the solution from both sides.
     293              :         double W_data[3];
     294              :         mfem::Vector W(W_data, V.Size());
     295            0 :         U.GetVectorValue(*FET.Elem2, FET.Elem2->GetIntPoint(), W);
     296            0 :         add(0.5, V, W, V);
     297              :       }
     298            0 :       return FET.Elem1->Attribute;
     299              :     }
     300            0 :     else if (use_elem2)
     301              :     {
     302            0 :       U.GetVectorValue(*FET.Elem2, FET.Elem2->GetIntPoint(), V);
     303            0 :       return FET.Elem2->Attribute;
     304              :     }
     305              :     else
     306              :     {
     307              :       return 0;
     308              :     }
     309              :   }
     310              : 
     311              : public:
     312            0 :   InterfaceDielectricCoefficient(const GridFunction &E, const MaterialOperator &mat_op,
     313              :                                  double t_i, double epsilon_i)
     314            0 :     : mfem::Coefficient(), BdrGridFunctionCoefficient(*E.ParFESpace()->GetParMesh()), E(E),
     315            0 :       mat_op(mat_op), t_i(t_i), epsilon_i(epsilon_i)
     316              :   {
     317              :   }
     318              : 
     319              :   double Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override;
     320              : };
     321              : 
     322              : template <>
     323            0 : inline double InterfaceDielectricCoefficient<InterfaceDielectric::DEFAULT>::Eval(
     324              :     mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip)
     325              : {
     326              :   // Get single-sided solution. Don't use lightspeed detection for differentiating side.
     327            0 :   auto GetLocalVectorValueDefault = [this](const mfem::ParGridFunction &U, mfem::Vector &V)
     328              :   {
     329            0 :     U.GetVectorValue(*FET.Elem1, FET.Elem1->GetIntPoint(), V);
     330            0 :     if (FET.Elem2)
     331              :     {
     332              :       // Double-sided, not a true boundary. Just average the field solution from both sides.
     333              :       double W_data[3];
     334              :       mfem::Vector W(W_data, V.Size());
     335            0 :       U.GetVectorValue(*FET.Elem2, FET.Elem2->GetIntPoint(), W);
     336            0 :       add(0.5, V, W, V);
     337              :     }
     338            0 :   };
     339              :   double V_data[3];
     340            0 :   mfem::Vector V(V_data, T.GetSpaceDim());
     341              :   Initialize(T, ip, nullptr);
     342            0 :   GetLocalVectorValueDefault(E.Real(), V);
     343            0 :   double V2 = V * V;
     344            0 :   if (E.HasImag())
     345              :   {
     346            0 :     GetLocalVectorValueDefault(E.Imag(), V);
     347            0 :     V2 += V * V;
     348              :   }
     349              : 
     350              :   // No specific interface, use full field evaluation: 0.5 * t * ε * |E|² .
     351            0 :   return 0.5 * t_i * epsilon_i * V2;
     352              : }
     353              : 
     354              : template <>
     355            0 : inline double InterfaceDielectricCoefficient<InterfaceDielectric::MA>::Eval(
     356              :     mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip)
     357              : {
     358              :   // Get single-sided solution on air (vacuum) side and neighboring element attribute.
     359              :   double V_data[3], normal_data[3];
     360            0 :   mfem::Vector V(V_data, T.GetSpaceDim()), normal(normal_data, T.GetSpaceDim());
     361            0 :   Initialize(T, ip, &normal);
     362            0 :   int attr = GetLocalVectorValue(E.Real(), V, true);
     363            0 :   if (attr <= 0)
     364              :   {
     365              :     return 0.0;
     366              :   }
     367            0 :   double Vn = V * normal;
     368            0 :   double Vn2 = Vn * Vn;
     369            0 :   if (E.HasImag())
     370              :   {
     371            0 :     GetLocalVectorValue(E.Imag(), V, true);
     372            0 :     Vn = V * normal;
     373            0 :     Vn2 += Vn * Vn;
     374              :   }
     375              : 
     376              :   // Metal-air interface: 0.5 * t / ε_MA * |E_n|² .
     377            0 :   return 0.5 * (t_i / epsilon_i) * Vn2;
     378              : }
     379              : 
     380              : template <>
     381            0 : inline double InterfaceDielectricCoefficient<InterfaceDielectric::MS>::Eval(
     382              :     mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip)
     383              : {
     384              :   // Get single-sided solution on substrate side and neighboring element attribute.
     385              :   double V_data[3], W_data[3], normal_data[3];
     386            0 :   mfem::Vector V(V_data, T.GetSpaceDim()), W(W_data, T.GetSpaceDim()),
     387            0 :       normal(normal_data, T.GetSpaceDim());
     388            0 :   Initialize(T, ip, &normal);
     389            0 :   int attr = GetLocalVectorValue(E.Real(), V, false);
     390            0 :   if (attr <= 0)
     391              :   {
     392              :     return 0.0;
     393              :   }
     394            0 :   mat_op.GetPermittivityReal(attr).Mult(V, W);
     395            0 :   double Vn = W * normal;
     396            0 :   double Vn2 = Vn * Vn;
     397            0 :   if (E.HasImag())
     398              :   {
     399            0 :     GetLocalVectorValue(E.Imag(), V, false);
     400            0 :     mat_op.GetPermittivityReal(attr).Mult(V, W);
     401            0 :     Vn = W * normal;
     402            0 :     Vn2 += Vn * Vn;
     403              :   }
     404              : 
     405              :   // Metal-substrate interface: 0.5 * t / ε_MS * |(ε_S E)_n|² .
     406            0 :   return 0.5 * (t_i / epsilon_i) * Vn2;
     407              : }
     408              : 
     409              : template <>
     410            0 : inline double InterfaceDielectricCoefficient<InterfaceDielectric::SA>::Eval(
     411              :     mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip)
     412              : {
     413              :   // Get single-sided solution on air side and neighboring element attribute.
     414              :   double V_data[3], normal_data[3];
     415            0 :   mfem::Vector V(V_data, T.GetSpaceDim()), normal(normal_data, T.GetSpaceDim());
     416            0 :   Initialize(T, ip, &normal);
     417            0 :   int attr = GetLocalVectorValue(E.Real(), V, true);
     418            0 :   if (attr <= 0)
     419              :   {
     420              :     return 0.0;
     421              :   }
     422            0 :   double Vn = V * normal;
     423            0 :   V.Add(-Vn, normal);
     424            0 :   double Vn2 = Vn * Vn;
     425            0 :   double Vt2 = V * V;
     426            0 :   if (E.HasImag())
     427              :   {
     428            0 :     GetLocalVectorValue(E.Imag(), V, true);
     429            0 :     Vn = V * normal;
     430            0 :     V.Add(-Vn, normal);
     431            0 :     Vn2 += Vn * Vn;
     432            0 :     Vt2 += V * V;
     433              :   }
     434              : 
     435              :   // Substrate-air interface: 0.5 * t * (ε_SA * |E_t|² + 1 / ε_SA * |E_n|²) .
     436            0 :   return 0.5 * t_i * ((epsilon_i * Vt2) + (Vn2 / epsilon_i));
     437              : }
     438              : 
     439              : // Helper for EnergyDensityCoefficient.
     440              : enum class EnergyDensityType
     441              : {
     442              :   ELECTRIC,
     443              :   MAGNETIC
     444              : };
     445              : 
     446              : // Returns the local energy density evaluated as 1/2 Dᴴ E or 1/2 Hᴴ B for real-valued
     447              : // material coefficients. For internal boundary elements, the solution is averaged across
     448              : // the interface.
     449              : template <EnergyDensityType Type>
     450              : class EnergyDensityCoefficient : public mfem::Coefficient, public BdrGridFunctionCoefficient
     451              : {
     452              : private:
     453              :   const GridFunction &U;
     454              :   const MaterialOperator &mat_op;
     455              : 
     456              :   double GetLocalEnergyDensity(mfem::ElementTransformation &T) const;
     457              : 
     458              : public:
     459           24 :   EnergyDensityCoefficient(const GridFunction &U, const MaterialOperator &mat_op)
     460           24 :     : mfem::Coefficient(), BdrGridFunctionCoefficient(*U.ParFESpace()->GetParMesh()), U(U),
     461           24 :       mat_op(mat_op)
     462              :   {
     463              :   }
     464              : 
     465        25920 :   double Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override
     466              :   {
     467        25920 :     if (T.ElementType == mfem::ElementTransformation::ELEMENT)
     468              :     {
     469        20736 :       return GetLocalEnergyDensity(T);
     470              :     }
     471         5184 :     else if (T.ElementType == mfem::ElementTransformation::BDR_ELEMENT)
     472              :     {
     473              :       // Get neighboring elements.
     474         5184 :       GetBdrElementNeighborTransformations(T.ElementNo, ip);
     475              : 
     476              :       // For interior faces, compute the average value.
     477         5184 :       if (FET.Elem2)
     478              :       {
     479              :         return 0.5 *
     480            0 :                (GetLocalEnergyDensity(*FET.Elem1) + GetLocalEnergyDensity(*FET.Elem2));
     481              :       }
     482              :       else
     483              :       {
     484         5184 :         return GetLocalEnergyDensity(*FET.Elem1);
     485              :       }
     486              :     }
     487            0 :     MFEM_ABORT("Unsupported element type in EnergyDensityCoefficient!");
     488              :     return 0.0;
     489              :   }
     490              : };
     491              : 
     492              : template <>
     493        12960 : inline double EnergyDensityCoefficient<EnergyDensityType::ELECTRIC>::GetLocalEnergyDensity(
     494              :     mfem::ElementTransformation &T) const
     495              : {
     496              :   // Only the real part of the permittivity contributes to the energy (imaginary part
     497              :   // cancels out in the inner product due to symmetry).
     498              :   double V_data[3];
     499        12960 :   mfem::Vector V(V_data, T.GetSpaceDim());
     500        12960 :   U.Real().GetVectorValue(T, T.GetIntPoint(), V);
     501        25920 :   double dot = mat_op.GetPermittivityReal(T.Attribute).InnerProduct(V, V);
     502        12960 :   if (U.HasImag())
     503              :   {
     504         6480 :     U.Imag().GetVectorValue(T, T.GetIntPoint(), V);
     505        12960 :     dot += mat_op.GetPermittivityReal(T.Attribute).InnerProduct(V, V);
     506              :   }
     507        12960 :   return 0.5 * dot;
     508              : }
     509              : 
     510              : template <>
     511        12960 : inline double EnergyDensityCoefficient<EnergyDensityType::MAGNETIC>::GetLocalEnergyDensity(
     512              :     mfem::ElementTransformation &T) const
     513              : {
     514              :   double V_data[3];
     515        12960 :   mfem::Vector V(V_data, T.GetSpaceDim());
     516        12960 :   U.Real().GetVectorValue(T, T.GetIntPoint(), V);
     517        25920 :   double dot = mat_op.GetInvPermeability(T.Attribute).InnerProduct(V, V);
     518        12960 :   if (U.HasImag())
     519              :   {
     520         6480 :     U.Imag().GetVectorValue(T, T.GetIntPoint(), V);
     521        12960 :     dot += mat_op.GetInvPermeability(T.Attribute).InnerProduct(V, V);
     522              :   }
     523        12960 :   return 0.5 * dot;
     524              : }
     525              : 
     526              : // Compute time-averaged Poynting vector Re{E x H⋆}, without the typical factor of 1/2. For
     527              : // internal boundary elements, the solution is taken as the average.
     528              : class PoyntingVectorCoefficient : public mfem::VectorCoefficient,
     529              :                                   public BdrGridFunctionCoefficient
     530              : {
     531              : private:
     532              :   const GridFunction &E, &B;
     533              :   const MaterialOperator &mat_op;
     534              : 
     535        11664 :   void GetLocalPower(mfem::ElementTransformation &T, mfem::Vector &V) const
     536              :   {
     537              :     double W1_data[3], W2_data[3];
     538        23328 :     mfem::Vector W1(W1_data, T.GetSpaceDim()), W2(W2_data, T.GetSpaceDim());
     539        11664 :     B.Real().GetVectorValue(T, T.GetIntPoint(), W1);
     540        11664 :     mat_op.GetInvPermeability(T.Attribute).Mult(W1, W2);
     541        11664 :     E.Real().GetVectorValue(T, T.GetIntPoint(), W1);
     542        11664 :     V.SetSize(vdim);
     543        11664 :     linalg::Cross3(W1, W2, V);
     544        11664 :     if (E.HasImag())
     545              :     {
     546         7776 :       B.Imag().GetVectorValue(T, T.GetIntPoint(), W1);
     547         7776 :       mat_op.GetInvPermeability(T.Attribute).Mult(W1, W2);
     548         7776 :       E.Imag().GetVectorValue(T, T.GetIntPoint(), W1);
     549         7776 :       linalg::Cross3(W1, W2, V, true);
     550              :     }
     551        11664 :   }
     552              : 
     553              : public:
     554            9 :   PoyntingVectorCoefficient(const GridFunction &E, const GridFunction &B,
     555              :                             const MaterialOperator &mat_op)
     556            9 :     : mfem::VectorCoefficient(E.VectorDim()),
     557            9 :       BdrGridFunctionCoefficient(*E.ParFESpace()->GetParMesh()), E(E), B(B), mat_op(mat_op)
     558              :   {
     559            9 :   }
     560              : 
     561              :   using mfem::VectorCoefficient::Eval;
     562        11664 :   void Eval(mfem::Vector &V, mfem::ElementTransformation &T,
     563              :             const mfem::IntegrationPoint &ip) override
     564              :   {
     565        11664 :     if (T.ElementType == mfem::ElementTransformation::ELEMENT)
     566              :     {
     567         9720 :       GetLocalPower(T, V);
     568         9720 :       return;
     569              :     }
     570         1944 :     else if (T.ElementType == mfem::ElementTransformation::BDR_ELEMENT)
     571              :     {
     572              :       // Get neighboring elements.
     573         1944 :       GetBdrElementNeighborTransformations(T.ElementNo, ip);
     574              : 
     575              :       // For interior faces, compute the value on the desired side.
     576         1944 :       GetLocalPower(*FET.Elem1, V);
     577         1944 :       if (FET.Elem2)
     578              :       {
     579              :         double W_data[3];
     580              :         mfem::Vector W(W_data, V.Size());
     581            0 :         GetLocalPower(*FET.Elem2, W);
     582            0 :         add(0.5, V, W, V);
     583              :       }
     584         1944 :       return;
     585              :     }
     586            0 :     MFEM_ABORT("Unsupported element type in PoyntingVectorCoefficient!");
     587              :   }
     588              : };
     589              : 
     590              : // Returns the local vector field evaluated on a boundary element. For internal boundary
     591              : // elements the solution is the average.
     592              : class BdrFieldVectorCoefficient : public mfem::VectorCoefficient,
     593              :                                   public BdrGridFunctionCoefficient
     594              : {
     595              : private:
     596              :   const mfem::ParGridFunction &U;
     597              : 
     598              : public:
     599           39 :   BdrFieldVectorCoefficient(const mfem::ParGridFunction &U)
     600           39 :     : mfem::VectorCoefficient(U.VectorDim()),
     601           39 :       BdrGridFunctionCoefficient(*U.ParFESpace()->GetParMesh()), U(U)
     602              :   {
     603           39 :   }
     604              : 
     605              :   using mfem::VectorCoefficient::Eval;
     606         8424 :   void Eval(mfem::Vector &V, mfem::ElementTransformation &T,
     607              :             const mfem::IntegrationPoint &ip) override
     608              :   {
     609              :     // Get neighboring elements.
     610              :     MFEM_ASSERT(T.ElementType == mfem::ElementTransformation::BDR_ELEMENT,
     611              :                 "Unexpected element type in BdrFieldVectorCoefficient!");
     612         8424 :     GetBdrElementNeighborTransformations(T.ElementNo, ip);
     613              : 
     614              :     // For interior faces, compute the average.
     615         8424 :     U.GetVectorValue(*FET.Elem1, FET.Elem1->GetIntPoint(), V);
     616         8424 :     if (FET.Elem2)
     617              :     {
     618              :       double W_data[3];
     619              :       mfem::Vector W(W_data, V.Size());
     620            0 :       U.GetVectorValue(*FET.Elem2, FET.Elem2->GetIntPoint(), W);
     621            0 :       add(0.5, V, W, V);
     622              :     }
     623         8424 :   }
     624              : };
     625              : 
     626              : // Returns the local scalar field evaluated on a boundary element. For internal boundary
     627              : // elements the solution is the average.
     628              : class BdrFieldCoefficient : public mfem::Coefficient, public BdrGridFunctionCoefficient
     629              : {
     630              : private:
     631              :   const mfem::ParGridFunction &U;
     632              : 
     633              : public:
     634              :   BdrFieldCoefficient(const mfem::ParGridFunction &U)
     635            3 :     : mfem::Coefficient(), BdrGridFunctionCoefficient(*U.ParFESpace()->GetParMesh()), U(U)
     636              :   {
     637              :   }
     638              : 
     639          648 :   double Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override
     640              :   {
     641              :     // Get neighboring elements.
     642              :     MFEM_ASSERT(T.ElementType == mfem::ElementTransformation::BDR_ELEMENT,
     643              :                 "Unexpected element type in BdrFieldCoefficient!");
     644          648 :     GetBdrElementNeighborTransformations(T.ElementNo, ip);
     645              : 
     646              :     // For interior faces, compute the average.
     647          648 :     if (FET.Elem2)
     648              :     {
     649            0 :       return 0.5 * (U.GetValue(*FET.Elem1, FET.Elem1->GetIntPoint()),
     650            0 :                     U.GetValue(*FET.Elem2, FET.Elem2->GetIntPoint()));
     651              :     }
     652              :     else
     653              :     {
     654          648 :       return U.GetValue(*FET.Elem1, FET.Elem1->GetIntPoint());
     655              :     }
     656              :   }
     657              : };
     658              : 
     659              : //
     660              : // More helpful coefficient types. Wrapper coefficients allow additions of scalar and vector
     661              : // or matrix coefficients. Restricted coefficients only compute the coefficient if for the
     662              : // given list of attributes. Sum coefficients own a list of coefficients to add.
     663              : //
     664              : 
     665              : class VectorWrappedCoefficient : public mfem::VectorCoefficient
     666              : {
     667              : private:
     668              :   std::unique_ptr<mfem::Coefficient> coeff;
     669              : 
     670              : public:
     671              :   VectorWrappedCoefficient(int dim, std::unique_ptr<mfem::Coefficient> &&coeff)
     672              :     : mfem::VectorCoefficient(dim), coeff(std::move(coeff))
     673              :   {
     674              :   }
     675              : 
     676              :   using mfem::VectorCoefficient::Eval;
     677            0 :   void Eval(mfem::Vector &V, mfem::ElementTransformation &T,
     678              :             const mfem::IntegrationPoint &ip) override
     679              :   {
     680            0 :     V.SetSize(vdim);
     681            0 :     V = coeff->Eval(T, ip);
     682            0 :   }
     683              : };
     684              : 
     685              : class MatrixWrappedCoefficient : public mfem::MatrixCoefficient
     686              : {
     687              : private:
     688              :   std::unique_ptr<mfem::Coefficient> coeff;
     689              : 
     690              : public:
     691              :   MatrixWrappedCoefficient(int dim, std::unique_ptr<mfem::Coefficient> &&coeff)
     692              :     : mfem::MatrixCoefficient(dim), coeff(std::move(coeff))
     693              :   {
     694              :   }
     695              : 
     696              :   void Eval(mfem::DenseMatrix &K, mfem::ElementTransformation &T,
     697              :             const mfem::IntegrationPoint &ip) override
     698              :   {
     699              :     K.Diag(coeff->Eval(T, ip), height);
     700              :   }
     701              : };
     702              : 
     703              : template <typename Coefficient>
     704              : class RestrictedCoefficient : public Coefficient
     705              : {
     706              : private:
     707              :   mfem::Array<int> attr_marker;
     708              : 
     709              : public:
     710              :   template <typename... T>
     711            0 :   RestrictedCoefficient(const mfem::Array<int> &attr_list, T &&...args)
     712              :     : Coefficient(std::forward<T>(args)...),
     713            0 :       attr_marker(mesh::AttrToMarker(attr_list.Size() ? attr_list.Max() : 0, attr_list))
     714              :   {
     715            0 :   }
     716              : 
     717            0 :   double Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override
     718              :   {
     719            0 :     return (T.Attribute > attr_marker.Size() || !attr_marker[T.Attribute - 1])
     720            0 :                ? 0.0
     721            0 :                : Coefficient::Eval(T, ip);
     722              :   }
     723              : };
     724              : 
     725              : template <typename Coefficient>
     726              : class RestrictedVectorCoefficient : public Coefficient
     727              : {
     728              : private:
     729              :   mfem::Array<int> attr_marker;
     730              : 
     731              : public:
     732              :   template <typename... T>
     733           21 :   RestrictedVectorCoefficient(const mfem::Array<int> &attr_list, T &&...args)
     734              :     : Coefficient(std::forward<T>(args)...),
     735           21 :       attr_marker(mesh::AttrToMarker(attr_list.Size() ? attr_list.Max() : 0, attr_list))
     736              :   {
     737           21 :   }
     738              : 
     739          756 :   void Eval(mfem::Vector &V, mfem::ElementTransformation &T,
     740              :             const mfem::IntegrationPoint &ip) override
     741              :   {
     742          756 :     if (T.Attribute > attr_marker.Size() || !attr_marker[T.Attribute - 1])
     743              :     {
     744            0 :       V.SetSize(this->vdim);
     745            0 :       V = 0.0;
     746              :     }
     747              :     else
     748              :     {
     749          324 :       Coefficient::Eval(V, T, ip);
     750              :     }
     751          756 :   }
     752              : };
     753              : 
     754              : template <typename Coefficient>
     755              : class RestrictedMatrixCoefficient : public Coefficient
     756              : {
     757              : private:
     758              :   mfem::Array<int> attr_marker;
     759              : 
     760              : public:
     761              :   template <typename... T>
     762              :   RestrictedMatrixCoefficient(const mfem::Array<int> &attr_list, T &&...args)
     763              :     : Coefficient(std::forward<T>(args)...),
     764              :       attr_marker(mesh::AttrToMarker(attr_list.Size() ? attr_list.Max() : 0, attr_list))
     765              :   {
     766              :   }
     767              : 
     768              :   void Eval(mfem::DenseMatrix &K, mfem::ElementTransformation &T,
     769              :             const mfem::IntegrationPoint &ip) override
     770              :   {
     771              :     if (T.Attribute > attr_marker.Size() || !attr_marker[T.Attribute - 1])
     772              :     {
     773              :       K.SetSize(this->height, this->width);
     774              :       K = 0.0;
     775              :     }
     776              :     else
     777              :     {
     778              :       Coefficient::Eval(K, T, ip);
     779              :     }
     780              :   }
     781              : };
     782              : 
     783              : class SumCoefficient : public mfem::Coefficient
     784              : {
     785              : private:
     786              :   std::vector<std::pair<std::unique_ptr<mfem::Coefficient>, double>> c;
     787              : 
     788              : public:
     789              :   SumCoefficient() : mfem::Coefficient() {}
     790              : 
     791              :   bool empty() const { return c.empty(); }
     792              : 
     793              :   void AddCoefficient(std::unique_ptr<mfem::Coefficient> &&coeff, double a = 1.0)
     794              :   {
     795              :     c.emplace_back(std::move(coeff), a);
     796              :   }
     797              : 
     798            0 :   double Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override
     799              :   {
     800              :     double val = 0.0;
     801            0 :     for (auto &[coeff, a] : c)
     802              :     {
     803            0 :       val += a * coeff->Eval(T, ip);
     804              :     }
     805            0 :     return val;
     806              :   }
     807              : };
     808              : 
     809           18 : class SumVectorCoefficient : public mfem::VectorCoefficient
     810              : {
     811              : private:
     812              :   std::vector<std::pair<std::unique_ptr<mfem::VectorCoefficient>, double>> c;
     813              : 
     814              : public:
     815           18 :   SumVectorCoefficient(int d) : mfem::VectorCoefficient(d) {}
     816              : 
     817              :   bool empty() const { return c.empty(); }
     818              : 
     819           21 :   void AddCoefficient(std::unique_ptr<mfem::VectorCoefficient> &&coeff, double a = 1.0)
     820              :   {
     821           21 :     MFEM_VERIFY(coeff->GetVDim() == vdim,
     822              :                 "Invalid VectorCoefficient dimensions for SumVectorCoefficient!");
     823           21 :     c.emplace_back(std::move(coeff), a);
     824           21 :   }
     825              : 
     826              :   void AddCoefficient(std::unique_ptr<mfem::Coefficient> &&coeff, double a = 1.0)
     827              :   {
     828              :     c.emplace_back(std::make_unique<VectorWrappedCoefficient>(vdim, std::move(coeff)), a);
     829              :   }
     830              : 
     831              :   using mfem::VectorCoefficient::Eval;
     832          756 :   void Eval(mfem::Vector &V, mfem::ElementTransformation &T,
     833              :             const mfem::IntegrationPoint &ip) override
     834              :   {
     835              :     double U_data[3];
     836          756 :     mfem::Vector U(U_data, vdim);
     837          756 :     V.SetSize(vdim);
     838          756 :     V = 0.0;
     839         1512 :     for (auto &[coeff, a] : c)
     840              :     {
     841          756 :       coeff->Eval(U, T, ip);
     842          756 :       V.Add(a, U);
     843              :     }
     844          756 :   }
     845              : };
     846              : 
     847              : class SumMatrixCoefficient : public mfem::MatrixCoefficient
     848              : {
     849              : private:
     850              :   std::vector<std::pair<std::unique_ptr<mfem::MatrixCoefficient>, double>> c;
     851              : 
     852              : public:
     853              :   SumMatrixCoefficient(int d) : mfem::MatrixCoefficient(d) {}
     854              :   SumMatrixCoefficient(int h, int w) : mfem::MatrixCoefficient(h, w) {}
     855              : 
     856              :   bool empty() const { return c.empty(); }
     857              : 
     858              :   void AddCoefficient(std::unique_ptr<mfem::MatrixCoefficient> &&coeff, double a)
     859              :   {
     860              :     MFEM_VERIFY(coeff->GetHeight() == height && coeff->GetWidth() == width,
     861              :                 "Invalid MatrixCoefficient dimensions for SumMatrixCoefficient!");
     862              :     c.emplace_back(std::move(coeff), a);
     863              :   }
     864              : 
     865              :   void AddCoefficient(std::unique_ptr<mfem::Coefficient> &&coeff, double a)
     866              :   {
     867              :     MFEM_VERIFY(width == height, "MatrixWrappedCoefficient can only be constructed for "
     868              :                                  "square MatrixCoefficient objects!");
     869              :     c.emplace_back(std::make_unique<MatrixWrappedCoefficient>(height, std::move(coeff)), a);
     870              :   }
     871              : 
     872              :   void Eval(mfem::DenseMatrix &K, mfem::ElementTransformation &T,
     873              :             const mfem::IntegrationPoint &ip) override
     874              :   {
     875              :     double M_data[9];
     876              :     mfem::DenseMatrix M(M_data, height, width);
     877              :     K.SetSize(height, width);
     878              :     K = 0.0;
     879              :     for (auto &[coeff, a] : c)
     880              :     {
     881              :       coeff->Eval(M, T, ip);
     882              :       K.Add(a, M);
     883              :     }
     884              :   }
     885              : };
     886              : 
     887              : }  // namespace palace
     888              : 
     889              : #endif  // PALACE_FEM_COEFFICIENT_HPP
        

Generated by: LCOV version 2.0-1