LCOV - code coverage report
Current view: top level - utils - iodata.cpp (source / functions) Coverage Total Hit
Test: Palace Coverage Report Lines: 69.5 % 200 139
Test Date: 2025-10-23 22:45:05 Functions: 100.0 % 6 6
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 "iodata.hpp"
       5              : 
       6              : #include <charconv>
       7              : #include <fstream>
       8              : #include <functional>
       9              : #include <iostream>
      10              : #include <regex>
      11              : #include <sstream>
      12              : #include <stack>
      13              : #include <string>
      14              : #include <string_view>
      15              : #include <mfem.hpp>
      16              : #include <nlohmann/json.hpp>
      17              : #include "fem/bilinearform.hpp"
      18              : #include "fem/integrator.hpp"
      19              : #include "utils/communication.hpp"
      20              : #include "utils/geodata.hpp"
      21              : 
      22              : namespace palace
      23              : {
      24              : 
      25           26 : std::stringstream PreprocessFile(const char *filename)
      26              : {
      27              :   // Read configuration file into memory and return as a stringstream.
      28              :   std::string file;
      29              :   {
      30           26 :     std::ifstream fi(filename);
      31           26 :     std::stringstream buf;
      32           26 :     if (!fi.is_open())
      33              :     {
      34            0 :       MFEM_ABORT("Unable to open configuration file \"" << filename << "\"!");
      35              :     }
      36           26 :     buf << fi.rdbuf();
      37           26 :     fi.close();
      38           26 :     file = buf.str();
      39           26 :   }
      40              : 
      41              :   // Strip C and C++ style comments (//, /* */) using regex. Correctly handles comments
      42              :   // within strings and escaped comment markers (see tinyurl.com/2s3n8dkr). An alternative
      43              :   // for the middle line is: R"(|(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/))", but this
      44              :   // seems to sometimes lead to issues with std::regex_replace for long files.
      45              :   {
      46              :     std::regex rgx(R"((([\"'])(?:(?=(\\?))\3.)*?\2))"
      47              :                    R"(|(\/\*(.|[\r\n])*?\*\/))"
      48           26 :                    R"(|(\/\/.*))");
      49           26 :     file = std::regex_replace(file, rgx, "$1");
      50           26 :   }
      51              : 
      52              :   // Also strip whitespace.
      53              :   {
      54              :     std::regex rgx(R"((([\"'])(?:(?=(\\?))\3.)*?\2))"
      55           26 :                    R"(|(\s+))");
      56           26 :     file = std::regex_replace(file, rgx, "$1");
      57           26 :   }
      58              : 
      59              :   // Also strip erroneous trailing commas.
      60              :   {
      61              :     std::regex rgx(R"((([\"'])(?:(?=(\\?))\3.)*?\2))"
      62           26 :                    R"(|,+(?=\s*?[\}\]]))");
      63           26 :     file = std::regex_replace(file, rgx, "$1");
      64           26 :   }
      65              : 
      66              :   // Perform integer range expansion for arrays ([a - b, c] = [a-b,c] =
      67              :   // [a,a+1,...,b-1,b,c]). The whole file is now one line and arrays have no spaces after
      68              :   // whitespace stripping.
      69           26 :   std::stringstream output;
      70         1114 :   auto RangeExpand = [](std::string_view str) -> std::string
      71              :   {
      72              :     // Handle the given string which is only numeric with possible hyphens.
      73         1114 :     if (str.empty())
      74              :     {
      75            0 :       return "";
      76              :     }
      77              :     int num;
      78         1114 :     auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.length(), num);
      79         1114 :     MFEM_VERIFY(
      80              :         ec == std::errc(),
      81              :         "Invalid integer conversion in range expansion"
      82              :             << (ec == std::errc::result_out_of_range ? " (integer out of range)!" : "!"));
      83         1114 :     if (ptr == str.data() + str.length())
      84              :     {
      85         1114 :       return std::string(str);
      86              :     }
      87              :     // Range specified, expand the bounds.
      88              :     int num2;
      89            0 :     auto [ptr2, ec2] = std::from_chars(ptr + 1, str.data() + str.length(), num2);
      90            0 :     MFEM_VERIFY(
      91              :         ec2 == std::errc(),
      92              :         "Invalid integer conversion in range expansion"
      93              :             << (ec2 == std::errc::result_out_of_range ? " (integer out of range)!" : "!"));
      94              :     std::string rng;
      95            0 :     while (num < num2)
      96              :     {
      97            0 :       rng += std::to_string(num++) + ",";
      98              :     }
      99            0 :     rng += std::to_string(num);
     100            0 :     return rng;
     101              :   };
     102              :   {
     103           26 :     const std::string range_vals = "-0123456789,";
     104              :     auto start = file.begin();
     105              :     bool inside = false;
     106        84362 :     for (auto it = start; it != file.end(); ++it)
     107              :     {
     108        84336 :       if (inside)
     109              :       {
     110         3304 :         if (*it == ']')
     111              :         {
     112              :           // Apply integer range expansion (as needed) to the array, which includes only
     113              :           // digits, commas, and '-'. Exclude the outer square brackets.
     114         1066 :           std::string_view str(file.data() + (start - file.cbegin() + 1), it - start - 1);
     115              :           std::size_t s = 0, pos;
     116         1066 :           output << '[';
     117         1114 :           while ((pos = str.find(',', s)) != std::string::npos)
     118              :           {
     119           96 :             output << RangeExpand(str.substr(s, pos - s)) << ',';
     120           48 :             s = pos + 1;
     121              :           }
     122         3198 :           output << RangeExpand(str.substr(s)) << ']';
     123              :           start = it + 1;
     124              :           inside = false;
     125              :         }
     126         2238 :         else if (*it == '[')
     127              :         {
     128            0 :           output << std::string(start, it);
     129              :           start = it;
     130              :         }
     131         2238 :         else if (range_vals.find(*it) == std::string::npos)
     132              :         {
     133         1998 :           output << std::string(start, it);
     134              :           start = it;
     135              :           inside = false;
     136              :         }
     137              :       }
     138        81032 :       else if (*it == '[')
     139              :       {
     140         5196 :         output << std::string(start, it);
     141              :         start = it;
     142              :         inside = true;
     143              :       }
     144              :     }
     145           78 :     output << std::string(start, file.end());
     146              :   }
     147           26 :   return output;
     148            0 : }
     149              : 
     150              : using json = nlohmann::json;
     151              : 
     152            8 : IoData::IoData(const char *filename, bool print) : units(1.0, 1.0), init(false)
     153              : {
     154              :   // Open configuration file and preprocess: strip whitespace, comments, and expand integer
     155              :   // ranges.
     156            8 :   std::stringstream buffer = PreprocessFile(filename);
     157              : 
     158              :   // Parse the configuration file. Use a callback function to detect and throw errors for
     159              :   // duplicate keys.
     160              :   json config;
     161              :   std::stack<std::set<json>> parse_stack;
     162              :   json::parser_callback_t check_duplicate_keys =
     163         1932 :       [&](int, json::parse_event_t event, json &parsed)
     164              :   {
     165         1932 :     switch (event)
     166              :     {
     167          192 :       case json::parse_event_t::object_start:
     168          192 :         parse_stack.push(std::set<json>());
     169          192 :         break;
     170          192 :       case json::parse_event_t::object_end:
     171          192 :         parse_stack.pop();
     172              :         break;
     173          650 :       case json::parse_event_t::key:
     174              :         {
     175          650 :           const auto result = parse_stack.top().insert(parsed);
     176          650 :           if (!result.second)
     177              :           {
     178            0 :             MFEM_ABORT("Error parsing configuration file!\nDuplicate key "
     179              :                        << parsed << " was already seen in this object!");
     180              :             return false;
     181              :           }
     182              :         }
     183          650 :         break;
     184              :       default:
     185              :         break;
     186              :     }
     187              :     return true;
     188              :   };
     189              :   try
     190              :   {
     191            8 :     config = json::parse(buffer, check_duplicate_keys);
     192              :   }
     193            0 :   catch (json::parse_error &e)
     194              :   {
     195            0 :     MFEM_ABORT("Error parsing configuration file!\n  " << e.what());
     196            0 :   }
     197            8 :   if (print)
     198              :   {
     199            0 :     Mpi::Print("\n{}\n", config.dump(2));
     200              :   }
     201              : 
     202              :   // Set up configuration option data structures.
     203            8 :   problem.SetUp(config);
     204            8 :   model.SetUp(config);
     205            8 :   domains.SetUp(config);
     206            8 :   boundaries.SetUp(config);
     207            8 :   solver.SetUp(config);
     208              : 
     209              :   // Cleanup and error checking.
     210              :   config.erase("Problem");
     211              :   config.erase("Model");
     212              :   config.erase("Domains");
     213              :   config.erase("Boundaries");
     214              :   config.erase("Solver");
     215            8 :   MFEM_VERIFY(config.empty(), "Found an unsupported configuration file section!\n"
     216              :                                   << config.dump(2));
     217              : 
     218              :   // Check compatibility of configuration file and problem type.
     219            8 :   CheckConfiguration();
     220            8 : }
     221              : 
     222            8 : void IoData::CheckConfiguration()
     223              : {
     224              :   // Check that the provided domain and boundary objects are all supported by the requested
     225              :   // problem type.
     226            8 :   if (problem.type == ProblemType::DRIVEN)
     227              :   {
     228              :     // No unsupported domain or boundary objects for frequency domain driven simulations.
     229              :   }
     230              :   else if (problem.type == ProblemType::EIGENMODE)
     231              :   {
     232              :     // No unsupported domain or boundary objects for frequency domain driven simulations.
     233              :   }
     234              :   else if (problem.type == ProblemType::ELECTROSTATIC)
     235              :   {
     236            0 :     if (!boundaries.farfield.empty())
     237              :     {
     238            0 :       Mpi::Warning(
     239              :           "Electrostatic problem type does not support absorbing boundary conditions!\n");
     240              :     }
     241            0 :     if (!boundaries.conductivity.empty())
     242              :     {
     243            0 :       Mpi::Warning("Electrostatic problem type does not support surface conductivity "
     244              :                    "boundary conditions!\n");
     245              :     }
     246            0 :     if (!boundaries.impedance.empty())
     247              :     {
     248            0 :       Mpi::Warning("Electrostatic problem type does not support surface impedance boundary "
     249              :                    "conditions!\n");
     250              :     }
     251            0 :     if (!boundaries.auxpec.empty() || !boundaries.waveport.empty())
     252              :     {
     253            0 :       Mpi::Warning(
     254              :           "Electrostatic problem type does not support wave port boundary conditions!\n");
     255              :     }
     256            0 :     if (!boundaries.current.empty())
     257              :     {
     258            0 :       Mpi::Warning(
     259              :           "Electrostatic problem type does not support surface current excitation!\n");
     260              :     }
     261              :   }
     262              :   else if (problem.type == ProblemType::MAGNETOSTATIC)
     263              :   {
     264            0 :     if (!boundaries.farfield.empty())
     265              :     {
     266            0 :       Mpi::Warning(
     267              :           "Magnetostatic problem type does not support absorbing boundary conditions!\n");
     268              :     }
     269            0 :     if (!boundaries.conductivity.empty())
     270              :     {
     271            0 :       Mpi::Warning("Magnetostatic problem type does not support surface conductivity "
     272              :                    "boundary conditions!\n");
     273              :     }
     274            0 :     if (!boundaries.impedance.empty())
     275              :     {
     276            0 :       Mpi::Warning("Magnetostatic problem type does not support surface impedance boundary "
     277              :                    "conditions!\n");
     278              :     }
     279            0 :     if (!boundaries.lumpedport.empty())
     280              :     {
     281            0 :       Mpi::Warning(
     282              :           "Magnetostatic problem type does not support lumped port boundary conditions!\n");
     283              :     }
     284            0 :     if (!boundaries.auxpec.empty() || !boundaries.waveport.empty())
     285              :     {
     286            0 :       Mpi::Warning(
     287              :           "Magnetostatic problem type does not support wave port boundary conditions!\n");
     288              :     }
     289            0 :     if (!boundaries.postpro.dielectric.empty())
     290              :     {
     291            0 :       Mpi::Warning("Magnetostatic problem type does not support boundary interface "
     292              :                    "dielectric loss postprocessing!\n");
     293              :     }
     294              :   }
     295              :   else if (problem.type == ProblemType::TRANSIENT)
     296              :   {
     297            0 :     if (!boundaries.conductivity.empty())
     298              :     {
     299            0 :       Mpi::Warning("Transient problem type does not support surface conductivity boundary "
     300              :                    "conditions!\n");
     301              :     }
     302            0 :     if (!boundaries.auxpec.empty() || !boundaries.waveport.empty())
     303              :     {
     304            0 :       Mpi::Warning(
     305              :           "Transient problem type does not support wave port boundary conditions!\n");
     306              :     }
     307            0 :     if (!boundaries.farfield.empty() && boundaries.farfield.order > 1)
     308              :     {
     309            0 :       Mpi::Warning("Transient problem type does not support absorbing boundary conditions "
     310              :                    "with order > 1!\n");
     311              :     }
     312              :   }
     313              : 
     314              :   // Resolve default values in configuration file.
     315            8 :   if (solver.linear.type == LinearSolver::DEFAULT)
     316              :   {
     317            8 :     if (problem.type == ProblemType::ELECTROSTATIC)
     318              :     {
     319            0 :       solver.linear.type = LinearSolver::BOOMER_AMG;
     320              :     }
     321            8 :     else if (problem.type == ProblemType::MAGNETOSTATIC ||
     322              :              problem.type == ProblemType::TRANSIENT)
     323              :     {
     324            0 :       solver.linear.type = LinearSolver::AMS;
     325              :     }
     326              :     else
     327              :     {
     328              :       // Prefer sparse direct solver for frequency domain problems if available.
     329              : #if defined(MFEM_USE_SUPERLU)
     330              :       solver.linear.type = LinearSolver::SUPERLU;
     331              : #elif defined(MFEM_USE_STRUMPACK)
     332            8 :       solver.linear.type = LinearSolver::STRUMPACK;
     333              : #elif defined(MFEM_USE_MUMPS)
     334              :       solver.linear.type = LinearSolver::MUMPS;
     335              : #else
     336              :       solver.linear.type = LinearSolver::AMS;
     337              : #endif
     338              :     }
     339              :   }
     340            8 :   if (solver.linear.krylov_solver == KrylovSolver::DEFAULT)
     341              :   {
     342              :     // Problems with SPD operators use CG by default, else GMRES.
     343            0 :     if (problem.type == ProblemType::ELECTROSTATIC ||
     344            0 :         problem.type == ProblemType::MAGNETOSTATIC ||
     345              :         problem.type == ProblemType::TRANSIENT)
     346              :     {
     347            0 :       solver.linear.krylov_solver = KrylovSolver::CG;
     348              :     }
     349              :     else
     350              :     {
     351            0 :       solver.linear.krylov_solver = KrylovSolver::GMRES;
     352              :     }
     353              :   }
     354            8 :   if (solver.linear.max_size < 0)
     355              :   {
     356            8 :     solver.linear.max_size = solver.linear.max_it;
     357              :   }
     358            8 :   if (solver.linear.initial_guess < 0)
     359              :   {
     360            8 :     if ((problem.type == ProblemType::DRIVEN && solver.driven.adaptive_tol <= 0.0) ||
     361            0 :         problem.type == ProblemType::TRANSIENT ||
     362            0 :         problem.type == ProblemType::ELECTROSTATIC ||
     363              :         problem.type == ProblemType::MAGNETOSTATIC)
     364              :     {
     365              :       // Default true only driven simulations without adaptive frequency sweep, transient
     366              :       // simulations, electrostatics, or magnetostatics.
     367            8 :       solver.linear.initial_guess = 1;
     368              :     }
     369              :     else
     370              :     {
     371            0 :       solver.linear.initial_guess = 0;
     372              :     }
     373              :   }
     374            8 :   if (solver.linear.pc_mat_shifted < 0)
     375              :   {
     376            8 :     if (problem.type == ProblemType::DRIVEN && solver.linear.type == LinearSolver::AMS)
     377              :     {
     378              :       // Default true only driven simulations using AMS (false for most cases).
     379            0 :       solver.linear.pc_mat_shifted = 1;
     380              :     }
     381              :     else
     382              :     {
     383            8 :       solver.linear.pc_mat_shifted = 0;
     384              :     }
     385              :   }
     386            8 :   if (solver.linear.mg_smooth_aux < 0)
     387              :   {
     388            8 :     if (problem.type == ProblemType::ELECTROSTATIC ||
     389              :         problem.type == ProblemType::MAGNETOSTATIC)
     390              :     {
     391              :       // Disable auxiliary space smoothing using distributive relaxation by default for
     392              :       // problems which don't need it.
     393            0 :       solver.linear.mg_smooth_aux = 0;
     394              :     }
     395              :     else
     396              :     {
     397            8 :       solver.linear.mg_smooth_aux = 1;
     398              :     }
     399              :   }
     400            8 :   if (solver.linear.mg_smooth_order < 0)
     401              :   {
     402            8 :     solver.linear.mg_smooth_order = std::max(2 * solver.order, 4);
     403              :   }
     404            8 :   if (solver.linear.ams_singular_op < 0)
     405              :   {
     406            8 :     solver.linear.ams_singular_op = (problem.type == ProblemType::MAGNETOSTATIC);
     407              :   }
     408            8 :   if (solver.linear.amg_agg_coarsen < 0)
     409              :   {
     410            8 :     solver.linear.amg_agg_coarsen = (problem.type == ProblemType::ELECTROSTATIC ||
     411            8 :                                      problem.type == ProblemType::MAGNETOSTATIC ||
     412              :                                      problem.type == ProblemType::TRANSIENT);
     413              :   }
     414              : 
     415              :   // Configure settings for quadrature rules and partial assembly.
     416            8 :   BilinearForm::pa_order_threshold = solver.pa_order_threshold;
     417            8 :   fem::DefaultIntegrationOrder::p_trial = solver.order;
     418            8 :   fem::DefaultIntegrationOrder::q_order_jac = solver.q_order_jac;
     419            8 :   fem::DefaultIntegrationOrder::q_order_extra_pk = solver.q_order_extra;
     420            8 :   fem::DefaultIntegrationOrder::q_order_extra_qk = solver.q_order_extra;
     421            8 : }
     422              : 
     423              : namespace
     424              : {
     425              : 
     426              : template <std::size_t N>
     427              : constexpr config::SymmetricMatrixData<N> &operator/=(config::SymmetricMatrixData<N> &data,
     428              :                                                      double s)
     429              : {
     430          104 :   for (auto &x : data.s)
     431              :   {
     432           78 :     x /= s;
     433              :   }
     434              :   return data;
     435              : }
     436              : 
     437              : }  // namespace
     438              : 
     439           26 : void IoData::NondimensionalizeInputs(mfem::ParMesh &mesh)
     440              : {
     441              :   // Nondimensionalization of the equations is based on a given length Lc in [m], typically
     442              :   // the largest domain dimension. Configuration file lengths and the mesh coordinates are
     443              :   // provided with units of model.L0 x [m].
     444           26 :   MFEM_VERIFY(!init, "NondimensionalizeInputs should only be called once!");
     445           26 :   init = true;
     446              : 
     447              :   // Calculate the reference length and time. A user specified model.Lc is in mesh length
     448              :   // units.
     449           26 :   if (model.Lc <= 0.0)
     450              :   {
     451              :     mfem::Vector bbmin, bbmax;
     452           26 :     mesh::GetAxisAlignedBoundingBox(mesh, bbmin, bbmax);
     453           26 :     bbmax -= bbmin;
     454           26 :     model.Lc = *std::max_element(bbmax.begin(), bbmax.end());
     455              :   }
     456              :   // Define units now mesh length set. Note: In model field Lc is measured in units of L0.
     457           26 :   units = Units(model.L0, model.Lc * model.L0);
     458              : 
     459              :   // Mesh refinement parameters.
     460              :   auto DivideLengthScale = [Lc0 = units.GetMeshLengthRelativeScale()](double val)
     461           96 :   { return val / Lc0; };
     462           26 :   for (auto &box : model.refinement.GetBoxes())
     463              :   {
     464              :     std::transform(box.bbmin.begin(), box.bbmin.end(), box.bbmin.begin(),
     465              :                    DivideLengthScale);
     466              :     std::transform(box.bbmax.begin(), box.bbmax.end(), box.bbmax.begin(),
     467              :                    DivideLengthScale);
     468              :   }
     469           26 :   for (auto &sphere : model.refinement.GetSpheres())
     470              :   {
     471            0 :     sphere.r /= units.GetMeshLengthRelativeScale();
     472              :     std::transform(sphere.center.begin(), sphere.center.end(), sphere.center.begin(),
     473              :                    DivideLengthScale);
     474              :   }
     475              : 
     476              :   // Materials: conductivity and London penetration depth.
     477           52 :   for (auto &data : domains.materials)
     478              :   {
     479              :     data.sigma /= units.GetScaleFactor<Units::ValueType::CONDUCTIVITY>();
     480           26 :     data.lambda_L /= units.GetMeshLengthRelativeScale();
     481              :   }
     482              : 
     483              :   // Probe location coordinates.
     484           42 :   for (auto &[idx, data] : domains.postpro.probe)
     485              :   {
     486              :     std::transform(data.center.begin(), data.center.end(), data.center.begin(),
     487              :                    DivideLengthScale);
     488              :   }
     489              : 
     490              :   // Finite conductivity boundaries.
     491           26 :   for (auto &data : boundaries.conductivity)
     492              :   {
     493            0 :     data.sigma /= units.GetScaleFactor<Units::ValueType::CONDUCTIVITY>();
     494            0 :     data.h /= units.GetMeshLengthRelativeScale();
     495              :   }
     496              : 
     497              :   // Impedance boundaries and lumped ports.
     498           26 :   for (auto &data : boundaries.impedance)
     499              :   {
     500            0 :     data.Rs /= units.GetScaleFactor<Units::ValueType::IMPEDANCE>();
     501            0 :     data.Ls /= units.GetScaleFactor<Units::ValueType::INDUCTANCE>();
     502            0 :     data.Cs /= units.GetScaleFactor<Units::ValueType::CAPACITANCE>();
     503              :   }
     504           50 :   for (auto &[idx, data] : boundaries.lumpedport)
     505              :   {
     506           24 :     data.R /= units.GetScaleFactor<Units::ValueType::IMPEDANCE>();
     507           24 :     data.L /= units.GetScaleFactor<Units::ValueType::INDUCTANCE>();
     508           24 :     data.C /= units.GetScaleFactor<Units::ValueType::CAPACITANCE>();
     509           24 :     data.Rs /= units.GetScaleFactor<Units::ValueType::IMPEDANCE>();
     510           24 :     data.Ls /= units.GetScaleFactor<Units::ValueType::INDUCTANCE>();
     511           24 :     data.Cs /= units.GetScaleFactor<Units::ValueType::CAPACITANCE>();
     512              :   }
     513              : 
     514              :   // Floquet periodic boundaries.
     515          104 :   for (auto &k : boundaries.periodic.wave_vector)
     516              :   {
     517           78 :     k *= units.GetMeshLengthRelativeScale();
     518              :   }
     519              : 
     520              :   // Wave port offset distance.
     521           26 :   for (auto &[idx, data] : boundaries.waveport)
     522              :   {
     523            0 :     data.d_offset /= units.GetMeshLengthRelativeScale();
     524              :   }
     525              : 
     526              :   // Center coordinates for surface flux.
     527           42 :   for (auto &[idx, data] : boundaries.postpro.flux)
     528              :   {
     529              :     std::transform(data.center.begin(), data.center.end(), data.center.begin(),
     530              :                    DivideLengthScale);
     531              :   }
     532              : 
     533              :   // Dielectric interface thickness.
     534           50 :   for (auto &[idx, data] : boundaries.postpro.dielectric)
     535              :   {
     536           24 :     data.t /= units.GetMeshLengthRelativeScale();
     537              :   }
     538              : 
     539              :   // For eigenmode simulations:
     540           26 :   solver.eigenmode.target /= units.GetScaleFactor<Units::ValueType::FREQUENCY>();
     541           26 :   solver.eigenmode.target_upper /= units.GetScaleFactor<Units::ValueType::FREQUENCY>();
     542              : 
     543              :   // For driven simulations:
     544           74 :   for (auto &f : solver.driven.sample_f)
     545           48 :     f /= units.GetScaleFactor<Units::ValueType::FREQUENCY>();
     546              : 
     547              :   // For transient simulations:
     548           26 :   solver.transient.pulse_f /= units.GetScaleFactor<Units::ValueType::FREQUENCY>();
     549           26 :   solver.transient.pulse_tau /= units.GetScaleFactor<Units::ValueType::TIME>();
     550           26 :   solver.transient.max_t /= units.GetScaleFactor<Units::ValueType::TIME>();
     551           26 :   solver.transient.delta_t /= units.GetScaleFactor<Units::ValueType::TIME>();
     552              : 
     553              :   // Scale mesh vertices for correct nondimensionalization.
     554           26 :   mesh::NondimensionalizeMesh(mesh, units.GetMeshLengthRelativeScale());
     555              : 
     556              :   // Print some information.
     557           26 :   Mpi::Print(mesh.GetComm(),
     558              :              "\nCharacteristic length and time scales:\n L₀ = {:.3e} m, t₀ = {:.3e} ns\n",
     559           26 :              units.GetScaleFactor<Units::ValueType::LENGTH>(),
     560           26 :              units.GetScaleFactor<Units::ValueType::TIME>());
     561           26 : }
     562              : 
     563              : }  // namespace palace
        

Generated by: LCOV version 2.0-1