LCOV - code coverage report
Current view: top level - models - surfacepostoperator.cpp (source / functions) Coverage Total Hit
Test: Palace Coverage Report Lines: 38.3 % 175 67
Test Date: 2025-10-23 22:45:05 Functions: 28.6 % 14 4
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              : #include "surfacepostoperator.hpp"
       5              : 
       6              : #include <complex>
       7              : #include <set>
       8              : #include "fem/gridfunction.hpp"
       9              : #include "fem/integrator.hpp"
      10              : #include "linalg/vector.hpp"
      11              : #include "models/materialoperator.hpp"
      12              : #include "models/strattonchu.hpp"
      13              : #include "utils/communication.hpp"
      14              : #include "utils/geodata.hpp"
      15              : #include "utils/iodata.hpp"
      16              : #include "utils/prettyprint.hpp"
      17              : #include "utils/timer.hpp"
      18              : 
      19              : namespace palace
      20              : {
      21              : 
      22              : namespace
      23              : {
      24              : 
      25              : template <typename T>
      26           17 : mfem::Array<int> SetUpBoundaryProperties(const T &data,
      27              :                                          const mfem::Array<int> &bdr_attr_marker)
      28              : {
      29              :   mfem::Array<int> attr_list;
      30           17 :   attr_list.Reserve(static_cast<int>(data.attributes.size()));
      31              :   std::set<int> bdr_warn_list;
      32           29 :   for (auto attr : data.attributes)
      33              :   {
      34              :     // MFEM_VERIFY(attr > 0 && attr <= bdr_attr_max,
      35              :     //             "Boundary postprocessing attribute tags must be non-negative and "
      36              :     //             "correspond to attributes in the mesh!");
      37              :     // MFEM_VERIFY(bdr_attr_marker[attr - 1],
      38              :     //             "Unknown boundary postprocessing attribute " << attr << "!");
      39           12 :     if (attr <= 0 || attr > bdr_attr_marker.Size() || !bdr_attr_marker[attr - 1])
      40              :     {
      41              :       bdr_warn_list.insert(attr);
      42              :     }
      43              :     else
      44              :     {
      45           12 :       attr_list.Append(attr);
      46              :     }
      47              :   }
      48           17 :   if (!bdr_warn_list.empty())
      49              :   {
      50            0 :     Mpi::Print("\n");
      51            0 :     Mpi::Warning(
      52              :         "Unknown boundary postprocessing attributes!\nSolver will just ignore them!");
      53            0 :     utils::PrettyPrint(bdr_warn_list, "Boundary attribute list:");
      54            0 :     Mpi::Print("\n");
      55              :   }
      56           17 :   return attr_list;
      57              : }
      58              : 
      59              : }  // namespace
      60              : 
      61            0 : SurfacePostOperator::SurfaceFluxData::SurfaceFluxData(
      62              :     const config::SurfaceFluxData &data, const mfem::ParMesh &mesh,
      63            0 :     const mfem::Array<int> &bdr_attr_marker)
      64              : {
      65              :   // Store boundary attributes for this postprocessing boundary.
      66            0 :   attr_list = SetUpBoundaryProperties(data, bdr_attr_marker);
      67              : 
      68              :   // Store the type of flux.
      69            0 :   switch (data.type)
      70              :   {
      71            0 :     case SurfaceFlux::ELECTRIC:
      72            0 :       type = SurfaceFlux::ELECTRIC;
      73            0 :       break;
      74            0 :     case SurfaceFlux::MAGNETIC:
      75            0 :       type = SurfaceFlux::MAGNETIC;
      76            0 :       break;
      77            0 :     case SurfaceFlux::POWER:
      78            0 :       type = SurfaceFlux::POWER;
      79            0 :       break;
      80              :   }
      81              : 
      82              :   // Store information about the global direction for orientation. Note the true boundary
      83              :   // normal is used in calculating the flux, this is just used to determine the sign.
      84            0 :   two_sided = data.two_sided;
      85            0 :   if (!two_sided)
      86              :   {
      87            0 :     center.SetSize(mesh.SpaceDimension());
      88            0 :     if (data.no_center)
      89              :     {
      90              :       // Compute the center as the bounding box centroid for all boundary elements making up
      91              :       // this postprocessing boundary.
      92              :       mfem::Vector bbmin, bbmax;
      93            0 :       mesh::GetAxisAlignedBoundingBox(
      94            0 :           mesh, mesh::AttrToMarker(bdr_attr_marker.Size(), attr_list), true, bbmin, bbmax);
      95            0 :       for (int d = 0; d < mesh.SpaceDimension(); d++)
      96              :       {
      97            0 :         center(d) = 0.5 * (bbmin(d) + bbmax(d));
      98              :       }
      99              :     }
     100              :     else
     101              :     {
     102              :       std::copy(data.center.begin(), data.center.end(), center.begin());
     103              :     }
     104              :   }
     105            0 : }
     106              : 
     107              : std::unique_ptr<mfem::Coefficient>
     108            0 : SurfacePostOperator::SurfaceFluxData::GetCoefficient(const mfem::ParGridFunction *E,
     109              :                                                      const mfem::ParGridFunction *B,
     110              :                                                      const MaterialOperator &mat_op) const
     111              : {
     112            0 :   switch (type)
     113              :   {
     114            0 :     case SurfaceFlux::ELECTRIC:
     115              :       return std::make_unique<
     116            0 :           RestrictedCoefficient<BdrSurfaceFluxCoefficient<SurfaceFlux::ELECTRIC>>>(
     117            0 :           attr_list, E, nullptr, mat_op, two_sided, center);
     118            0 :     case SurfaceFlux::MAGNETIC:
     119              :       return std::make_unique<
     120            0 :           RestrictedCoefficient<BdrSurfaceFluxCoefficient<SurfaceFlux::MAGNETIC>>>(
     121            0 :           attr_list, nullptr, B, mat_op, two_sided, center);
     122            0 :     case SurfaceFlux::POWER:
     123              :       return std::make_unique<
     124            0 :           RestrictedCoefficient<BdrSurfaceFluxCoefficient<SurfaceFlux::POWER>>>(
     125            0 :           attr_list, E, B, mat_op, two_sided, center);
     126              :   }
     127              :   return {};
     128              : }
     129              : 
     130            0 : SurfacePostOperator::InterfaceDielectricData::InterfaceDielectricData(
     131              :     const config::InterfaceDielectricData &data, const mfem::ParMesh &mesh,
     132            0 :     const mfem::Array<int> &bdr_attr_marker)
     133              : {
     134              :   // Store boundary attributes for this postprocessing boundary.
     135            0 :   attr_list = SetUpBoundaryProperties(data, bdr_attr_marker);
     136              : 
     137              :   // Calculate surface dielectric loss according to the formulas from J. Wenner et al.,
     138              :   // Surface loss simulations of superconducting coplanar waveguide resonators, Appl. Phys.
     139              :   // Lett. (2011). If only a general layer permittivity is specified and not any special
     140              :   // metal-air (MA), metal-substrate (MS), or substrate-air (SA) permittivity, compute the
     141              :   // numerator of the participation ratio according to the regular formula
     142              :   //                       p * E_elec = 1/2 t Re{∫ (ε E)ᴴ E_m dS} .
     143            0 :   switch (data.type)
     144              :   {
     145            0 :     case InterfaceDielectric::DEFAULT:
     146            0 :       type = InterfaceDielectric::DEFAULT;
     147            0 :       break;
     148            0 :     case InterfaceDielectric::MA:
     149            0 :       type = InterfaceDielectric::MA;
     150            0 :       break;
     151            0 :     case InterfaceDielectric::MS:
     152            0 :       type = InterfaceDielectric::MS;
     153            0 :       break;
     154            0 :     case InterfaceDielectric::SA:
     155            0 :       type = InterfaceDielectric::SA;
     156            0 :       break;
     157              :   }
     158            0 :   t = data.t;
     159            0 :   epsilon = data.epsilon_r;
     160            0 :   tandelta = data.tandelta;
     161            0 : }
     162              : 
     163              : std::unique_ptr<mfem::Coefficient>
     164            0 : SurfacePostOperator::InterfaceDielectricData::GetCoefficient(
     165              :     const GridFunction &E, const MaterialOperator &mat_op) const
     166              : {
     167            0 :   switch (type)
     168              :   {
     169            0 :     case InterfaceDielectric::DEFAULT:
     170              :       return std::make_unique<RestrictedCoefficient<
     171            0 :           InterfaceDielectricCoefficient<InterfaceDielectric::DEFAULT>>>(
     172            0 :           attr_list, E, mat_op, t, epsilon);
     173            0 :     case InterfaceDielectric::MA:
     174              :       return std::make_unique<
     175            0 :           RestrictedCoefficient<InterfaceDielectricCoefficient<InterfaceDielectric::MA>>>(
     176            0 :           attr_list, E, mat_op, t, epsilon);
     177            0 :     case InterfaceDielectric::MS:
     178              :       return std::make_unique<
     179            0 :           RestrictedCoefficient<InterfaceDielectricCoefficient<InterfaceDielectric::MS>>>(
     180            0 :           attr_list, E, mat_op, t, epsilon);
     181            0 :     case InterfaceDielectric::SA:
     182              :       return std::make_unique<
     183            0 :           RestrictedCoefficient<InterfaceDielectricCoefficient<InterfaceDielectric::SA>>>(
     184            0 :           attr_list, E, mat_op, t, epsilon);
     185              :   }
     186              :   return {};  // For compiler warning
     187              : }
     188              : 
     189           17 : SurfacePostOperator::FarFieldData::FarFieldData(const config::FarFieldPostData &data,
     190              :                                                 const mfem::ParMesh &mesh,
     191           17 :                                                 const mfem::Array<int> &bdr_attr_marker)
     192           17 :   : thetaphis(data.thetaphis)
     193              : {
     194              :   // Store boundary attributes for this postprocessing boundary.
     195           17 :   attr_list = SetUpBoundaryProperties(data, bdr_attr_marker);
     196           17 : }
     197              : 
     198           18 : SurfacePostOperator::SurfacePostOperator(const IoData &iodata,
     199              :                                          const MaterialOperator &mat_op,
     200              :                                          mfem::ParFiniteElementSpace &h1_fespace,
     201           18 :                                          mfem::ParFiniteElementSpace &nd_fespace)
     202           18 :   : mat_op(mat_op), h1_fespace(h1_fespace), nd_fespace(nd_fespace)
     203              : {
     204              :   // Check that boundary attributes have been specified correctly.
     205              :   const auto &mesh = *h1_fespace.GetParMesh();
     206           18 :   int bdr_attr_max = mesh.bdr_attributes.Size() ? mesh.bdr_attributes.Max() : 0;
     207              :   mfem::Array<int> bdr_attr_marker;
     208           18 :   if (!iodata.boundaries.postpro.flux.empty() ||
     209           36 :       !iodata.boundaries.postpro.dielectric.empty() ||
     210              :       !iodata.boundaries.postpro.farfield.empty())
     211              :   {
     212              :     bdr_attr_marker.SetSize(bdr_attr_max);
     213              :     bdr_attr_marker = 0;
     214           21 :     for (auto attr : mesh.bdr_attributes)
     215              :     {
     216           18 :       bdr_attr_marker[attr - 1] = 1;
     217              :     }
     218              :   }
     219              : 
     220              :   // Surface flux postprocessing.
     221           18 :   for (const auto &[idx, data] : iodata.boundaries.postpro.flux)
     222              :   {
     223            0 :     MFEM_VERIFY(iodata.problem.type != ProblemType::ELECTROSTATIC ||
     224              :                     data.type == SurfaceFlux::ELECTRIC,
     225              :                 "Magnetic field or power surface flux postprocessing are not available "
     226              :                 "for electrostatic problems!");
     227            0 :     MFEM_VERIFY(iodata.problem.type != ProblemType::MAGNETOSTATIC ||
     228              :                     data.type == SurfaceFlux::MAGNETIC,
     229              :                 "Electric field or power surface flux postprocessing are not available "
     230              :                 "for magnetostatic problems!");
     231            0 :     flux_surfs.try_emplace(idx, data, *h1_fespace.GetParMesh(), bdr_attr_marker);
     232              :   }
     233              : 
     234              :   // Interface dielectric postprocessing.
     235           18 :   MFEM_VERIFY(iodata.boundaries.postpro.dielectric.empty() ||
     236              :                   iodata.problem.type != ProblemType::MAGNETOSTATIC,
     237              :               "Interface dielectric loss postprocessing is not available for "
     238              :               "magnetostatic problems!");
     239           18 :   for (const auto &[idx, data] : iodata.boundaries.postpro.dielectric)
     240              :   {
     241            0 :     eps_surfs.try_emplace(idx, data, *h1_fespace.GetParMesh(), bdr_attr_marker);
     242              :   }
     243              : 
     244              :   // FarField postprocessing.
     245           18 :   MFEM_VERIFY(iodata.boundaries.postpro.farfield.empty() ||
     246              :                   iodata.problem.type == ProblemType::DRIVEN ||
     247              :                   iodata.problem.type == ProblemType::EIGENMODE,
     248              :               "Far-field extraction is only available for driven and eigenmode problems!");
     249              : 
     250              :   // Check that we don't have anisotropic materials.
     251           18 :   if (!iodata.boundaries.postpro.farfield.empty())
     252              :   {
     253              :     const auto &mesh = *nd_fespace.GetParMesh();
     254            3 :     int bdr_attr_max = mesh.bdr_attributes.Size() ? mesh.bdr_attributes.Max() : 0;
     255              :     mfem::Array<int> bdr_attr_marker =
     256            3 :         mesh::AttrToMarker(bdr_attr_max, iodata.boundaries.postpro.farfield.attributes);
     257              : 
     258              :     std::set<int> domain_attrs;
     259              : 
     260         9651 :     for (int i = 0; i < mesh.GetNBE(); i++)
     261              :     {
     262         9648 :       if (bdr_attr_marker[mesh.GetBdrAttribute(i) - 1])
     263              :       {
     264              :         int elem_id, _face_id;
     265         9608 :         mesh.GetBdrElementAdjacentElement(i, elem_id, _face_id);
     266         9608 :         if (elem_id >= 0)
     267              :         {
     268         9609 :           domain_attrs.insert(mesh.GetAttribute(elem_id));
     269              :         }
     270              :       }
     271              :     }
     272              : 
     273            5 :     for (int attr : domain_attrs)
     274              :     {
     275            7 :       MFEM_VERIFY(mat_op.IsIsotropic(attr),
     276              :                   "FarField requires isotropic materials, but attribute " +
     277              :                       std::to_string(attr) + " is not.");
     278              :     }
     279              :   }
     280              : 
     281           18 :   farfield = FarFieldData(iodata.boundaries.postpro.farfield, *nd_fespace.GetParMesh(),
     282           17 :                           bdr_attr_marker);
     283           18 : }
     284              : 
     285            0 : std::complex<double> SurfacePostOperator::GetSurfaceFlux(int idx, const GridFunction *E,
     286              :                                                          const GridFunction *B) const
     287              : {
     288              :   // For complex-valued fields, output the separate real and imaginary parts for the time-
     289              :   // harmonic quantity. For power flux (Poynting vector), output only the stationary real
     290              :   // part and not the part which has double the frequency.
     291              :   auto it = flux_surfs.find(idx);
     292            0 :   MFEM_VERIFY(it != flux_surfs.end(),
     293              :               "Unknown surface flux postprocessing index requested!");
     294            0 :   const bool has_imag = (E) ? E->HasImag() : B->HasImag();
     295            0 :   const auto &mesh = *h1_fespace.GetParMesh();
     296            0 :   int bdr_attr_max = mesh.bdr_attributes.Size() ? mesh.bdr_attributes.Max() : 0;
     297            0 :   mfem::Array<int> attr_marker = mesh::AttrToMarker(bdr_attr_max, it->second.attr_list);
     298              :   auto f =
     299            0 :       it->second.GetCoefficient(E ? &E->Real() : nullptr, B ? &B->Real() : nullptr, mat_op);
     300            0 :   std::complex<double> dot(GetLocalSurfaceIntegral(*f, attr_marker), 0.0);
     301            0 :   if (has_imag)
     302              :   {
     303            0 :     f = it->second.GetCoefficient(E ? &E->Imag() : nullptr, B ? &B->Imag() : nullptr,
     304              :                                   mat_op);
     305            0 :     double doti = GetLocalSurfaceIntegral(*f, attr_marker);
     306            0 :     if (it->second.type == SurfaceFlux::POWER)
     307              :     {
     308              :       dot += doti;
     309              :     }
     310              :     else
     311              :     {
     312              :       dot.imag(doti);
     313              :     }
     314              :   }
     315            0 :   Mpi::GlobalSum(1, &dot, (E) ? E->GetComm() : B->GetComm());
     316            0 :   return dot;
     317              : }
     318              : 
     319            0 : double SurfacePostOperator::GetInterfaceLossTangent(int idx) const
     320              : {
     321              :   auto it = eps_surfs.find(idx);
     322            0 :   MFEM_VERIFY(it != eps_surfs.end(),
     323              :               "Unknown interface dielectric postprocessing index requested!");
     324            0 :   return it->second.tandelta;
     325              : }
     326              : 
     327            0 : double SurfacePostOperator::GetInterfaceElectricFieldEnergy(int idx,
     328              :                                                             const GridFunction &E) const
     329              : {
     330              :   auto it = eps_surfs.find(idx);
     331            0 :   MFEM_VERIFY(it != eps_surfs.end(),
     332              :               "Unknown interface dielectric postprocessing index requested!");
     333            0 :   const auto &mesh = *h1_fespace.GetParMesh();
     334            0 :   int bdr_attr_max = mesh.bdr_attributes.Size() ? mesh.bdr_attributes.Max() : 0;
     335            0 :   mfem::Array<int> attr_marker = mesh::AttrToMarker(bdr_attr_max, it->second.attr_list);
     336            0 :   auto f = it->second.GetCoefficient(E, mat_op);
     337            0 :   double dot = GetLocalSurfaceIntegral(*f, attr_marker);
     338              :   Mpi::GlobalSum(1, &dot, E.GetComm());
     339            0 :   return dot;
     340              : }
     341              : 
     342              : double
     343            0 : SurfacePostOperator::GetLocalSurfaceIntegral(mfem::Coefficient &f,
     344              :                                              const mfem::Array<int> &attr_marker) const
     345              : {
     346              :   // Integrate the coefficient over the boundary attributes making up this surface index.
     347            0 :   mfem::LinearForm s(&h1_fespace);
     348            0 :   s.AddBoundaryIntegrator(new BoundaryLFIntegrator(f),
     349              :                           const_cast<mfem::Array<int> &>(attr_marker));
     350            0 :   s.UseFastAssembly(false);
     351              :   s.UseDevice(false);
     352            0 :   s.Assemble();
     353              :   s.UseDevice(true);
     354            0 :   return linalg::LocalSum(s);
     355            0 : }
     356              : 
     357            8 : std::vector<std::array<std::complex<double>, 3>> SurfacePostOperator::GetFarFieldrE(
     358              :     const std::vector<std::pair<double, double>> &theta_phi_pairs, const GridFunction &E,
     359              :     const GridFunction &B, double omega_re, double omega_im) const
     360              : {
     361            8 :   if (theta_phi_pairs.empty())
     362            6 :     return {};
     363            2 :   BlockTimer bt0(Timer::POSTPRO_FARFIELD);
     364              : 
     365              :   // Compute target unit vectors from the given theta and phis.
     366              :   std::vector<std::array<double, 3>> r_naughts;
     367            2 :   r_naughts.reserve(theta_phi_pairs.size());
     368              : 
     369            2 :   r_naughts.reserve(theta_phi_pairs.size());
     370         2050 :   for (const auto &[theta, phi] : theta_phi_pairs)
     371              :   {
     372         2048 :     r_naughts.emplace_back(std::array<double, 3>{
     373         2048 :         std::sin(theta) * std::cos(phi), std::sin(theta) * std::sin(phi), std::cos(theta)});
     374              :   }
     375              : 
     376            2 :   const auto &mesh = *nd_fespace.GetParMesh();
     377            2 :   int bdr_attr_max = mesh.bdr_attributes.Size() ? mesh.bdr_attributes.Max() : 0;
     378            2 :   mfem::Array<int> attr_marker = mesh::AttrToMarker(bdr_attr_max, farfield.attr_list);
     379              : 
     380              :   // Integrate. Each MPI process computes its contribution and we will reduce
     381              :   // everything at the end. We make them std::vector<std::array<double, 3>>
     382              :   // because we want a very simple memory layout so that we can reduce
     383              :   // everything with two MPI calls.
     384            2 :   std::vector<std::array<double, 3>> integrals_r(theta_phi_pairs.size());
     385            2 :   std::vector<std::array<double, 3>> integrals_i(theta_phi_pairs.size());
     386              : 
     387         9602 :   for (int i = 0; i < mesh.GetNBE(); i++)
     388              :   {
     389         9600 :     if (!attr_marker[mesh.GetBdrAttribute(i) - 1])
     390            0 :       continue;
     391              : 
     392         9600 :     auto *T = const_cast<mfem::ParMesh &>(mesh).GetBdrElementTransformation(i);
     393         9600 :     const auto *fe = nd_fespace.GetBE(i);
     394              :     const auto *ir =
     395         9600 :         &mfem::IntRules.Get(fe->GetGeomType(), fem::DefaultIntegrationOrder::Get(*T));
     396              : 
     397         9600 :     AddStrattonChuIntegrandAtElement(E, B, mat_op, omega_re, omega_im, r_naughts, *T, *ir,
     398              :                                      integrals_r, integrals_i);
     399              :   }
     400              : 
     401              :   double *data_r_ptr = integrals_r.data()->data();
     402              :   double *data_i_ptr = integrals_i.data()->data();
     403            2 :   size_t total_elements = integrals_r.size() * 3;
     404            2 :   Mpi::GlobalSum(total_elements, data_i_ptr, E.GetComm());
     405              :   Mpi::GlobalSum(total_elements, data_r_ptr, E.GetComm());
     406              : 
     407              :   // Finally, we apply cross product to reduced integrals and package the result
     408              :   // in a neatly accessible vector of arrays of complex numbers.
     409            2 :   std::vector<std::array<std::complex<double>, 3>> result(theta_phi_pairs.size());
     410            2 :   StaticVector<3> tmp_r, tmp_i;
     411         2050 :   for (size_t k = 0; k < theta_phi_pairs.size(); k++)
     412              :   {
     413         2048 :     linalg::Cross3(r_naughts[k], integrals_r[k], tmp_r);
     414         2048 :     linalg::Cross3(r_naughts[k], integrals_i[k], tmp_i);
     415         8192 :     for (size_t d = 0; d < 3; d++)
     416              :     {
     417         6144 :       result[k][d] = std::complex<double>{tmp_r[d], tmp_i[d]};
     418              :     }
     419              :   }
     420              :   return result;
     421            2 : }
     422              : 
     423              : }  // namespace palace
        

Generated by: LCOV version 2.0-1