LCOV - code coverage report
Current view: top level - drivers - basesolver.cpp (source / functions) Coverage Total Hit
Test: Palace Coverage Report Lines: 0.0 % 140 0
Test Date: 2025-10-23 22:45:05 Functions: 0.0 % 13 0
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 "basesolver.hpp"
       5              : 
       6              : #include <array>
       7              : #include <complex>
       8              : #include <numeric>
       9              : #include <mfem.hpp>
      10              : #include <nlohmann/json.hpp>
      11              : #include "drivers/transientsolver.hpp"
      12              : #include "fem/errorindicator.hpp"
      13              : #include "fem/fespace.hpp"
      14              : #include "fem/mesh.hpp"
      15              : #include "linalg/ksp.hpp"
      16              : #include "models/domainpostoperator.hpp"
      17              : #include "models/portexcitations.hpp"
      18              : #include "models/postoperator.hpp"
      19              : #include "models/surfacepostoperator.hpp"
      20              : #include "utils/communication.hpp"
      21              : #include "utils/dorfler.hpp"
      22              : #include "utils/filesystem.hpp"
      23              : #include "utils/geodata.hpp"
      24              : #include "utils/iodata.hpp"
      25              : #include "utils/timer.hpp"
      26              : 
      27              : namespace palace
      28              : {
      29              : 
      30              : using json = nlohmann::json;
      31              : 
      32              : namespace
      33              : {
      34              : 
      35            0 : void SaveIteration(MPI_Comm comm, const fs::path &output_dir, int step, int width)
      36              : {
      37            0 :   BlockTimer bt(Timer::IO);
      38              :   Mpi::Barrier(comm);  // Wait for all processes to write postprocessing files
      39            0 :   if (Mpi::Root(comm))
      40              :   {
      41              :     // Create a subfolder for the results of this adaptation.
      42            0 :     auto step_output = output_dir / fmt::format("iteration{:0{}d}", step, width);
      43              :     if (!fs::exists(step_output))
      44              :     {
      45            0 :       fs::create_directories(step_output);
      46              :     }
      47              :     constexpr auto options =
      48              :         fs::copy_options::recursive | fs::copy_options::overwrite_existing;
      49            0 :     for (const auto &f : fs::directory_iterator(output_dir))
      50              :     {
      51            0 :       if (f.path().filename().string().rfind("iteration") == 0)
      52              :       {
      53            0 :         continue;
      54              :       }
      55            0 :       fs::copy(f, step_output / f.path().filename(), options);
      56              :     }
      57            0 :   }
      58              :   Mpi::Barrier(comm);
      59            0 : }
      60              : 
      61            0 : json LoadMetadata(const fs::path &post_dir)
      62              : {
      63            0 :   std::string path = post_dir / "palace.json";
      64            0 :   std::ifstream fi(path);
      65            0 :   if (!fi.is_open())
      66              :   {
      67            0 :     MFEM_ABORT("Unable to open metadata file \"" << path << "\"!");
      68              :   }
      69            0 :   return json::parse(fi);
      70            0 : }
      71              : 
      72            0 : void WriteMetadata(const fs::path &post_dir, const json &meta)
      73              : {
      74            0 :   std::string path = post_dir / "palace.json";
      75            0 :   std::ofstream fo(path);
      76            0 :   if (!fo.is_open())
      77              :   {
      78            0 :     MFEM_ABORT("Unable to open metadata file \"" << path << "\"!");
      79              :   }
      80            0 :   fo << meta.dump(2) << '\n';
      81            0 : }
      82              : 
      83              : // Returns an array of indices corresponding to marked elements.
      84            0 : mfem::Array<int> MarkedElements(const Vector &e, double threshold)
      85              : {
      86              :   mfem::Array<int> ind;
      87              :   ind.Reserve(e.Size());
      88            0 :   for (int i = 0; i < e.Size(); i++)
      89              :   {
      90            0 :     if (e[i] >= threshold)
      91              :     {
      92            0 :       ind.Append(i);
      93              :     }
      94              :   }
      95            0 :   return ind;
      96              : }
      97              : 
      98              : }  // namespace
      99              : 
     100            0 : BaseSolver::BaseSolver(const IoData &iodata, bool root, int size, int num_thread,
     101            0 :                        const char *git_tag)
     102            0 :   : iodata(iodata), post_dir(iodata.problem.output), root(root)
     103              : {
     104              :   // Initialize simulation metadata for this simulation.
     105            0 :   if (root)
     106              :   {
     107              :     json meta;
     108            0 :     if (git_tag)
     109              :     {
     110            0 :       meta["GitTag"] = std::string(git_tag);
     111              :     }
     112            0 :     if (size > 0)
     113              :     {
     114            0 :       meta["Problem"]["MPISize"] = size;
     115              :     }
     116            0 :     if (num_thread > 0)
     117              :     {
     118            0 :       meta["Problem"]["OpenMPThreads"] = num_thread;
     119              :     }
     120            0 :     WriteMetadata(post_dir, meta);
     121              :   }
     122            0 : }
     123              : 
     124            0 : void BaseSolver::SolveEstimateMarkRefine(std::vector<std::unique_ptr<Mesh>> &mesh) const
     125              : {
     126            0 :   const auto &refinement = iodata.model.refinement;
     127            0 :   const bool use_amr = [&]()
     128              :   {
     129            0 :     if (refinement.max_it > 0 && dynamic_cast<const TransientSolver *>(this) != nullptr)
     130              :     {
     131            0 :       Mpi::Warning("AMR is not currently supported for transient simulations!\n");
     132            0 :       return false;
     133              :     }
     134            0 :     return (refinement.max_it > 0);
     135            0 :   }();
     136            0 :   if (use_amr && mesh.size() > 1)
     137              :   {
     138            0 :     Mpi::Print("\nFlattening mesh sequence:\n AMR will start from the final mesh in "
     139              :                "the sequence of a priori refinements\n");
     140              :     mesh.erase(mesh.begin(), mesh.end() - 1);
     141              :   }
     142            0 :   MPI_Comm comm = mesh.back()->GetComm();
     143              : 
     144              :   // Perform initial solve and estimation.
     145            0 :   auto [indicators, ntdof] = Solve(mesh);
     146            0 :   double err = indicators.Norml2(comm);
     147              : 
     148              :   // Collection of all tests that might exhaust resources.
     149              :   auto ExhaustedResources = [&refinement](auto it, auto ntdof)
     150              :   {
     151              :     bool ret = false;
     152              :     // Run out of iterations.
     153            0 :     ret |= (it >= refinement.max_it);
     154              :     // Run out of DOFs if a limit was set.
     155            0 :     ret |= (refinement.max_size > 0 && ntdof > refinement.max_size);
     156              :     return ret;
     157              :   };
     158              : 
     159              :   // Main AMR loop.
     160            0 :   int it = 0;
     161            0 :   while (!ExhaustedResources(it, ntdof) && err >= refinement.tol)
     162              :   {
     163              :     // Print timing summary.
     164            0 :     Mpi::Print("\nCumulative timing statistics:\n");
     165            0 :     BlockTimer::Print(comm);
     166            0 :     SaveMetadata(BlockTimer::GlobalTimer());
     167              : 
     168            0 :     BlockTimer bt(Timer::ADAPTATION);
     169            0 :     Mpi::Print("\nAdaptive mesh refinement (AMR) iteration {:d}:\n"
     170              :                " Indicator norm = {:.3e}, global unknowns = {:d}\n"
     171              :                " Max. iterations = {:d}, tol. = {:.3e}{}\n",
     172            0 :                ++it, err, ntdof, refinement.max_it, refinement.tol,
     173            0 :                (refinement.max_size > 0
     174            0 :                     ? ", max. size = " + std::to_string(refinement.max_size)
     175            0 :                     : ""));
     176              : 
     177              :     // Optionally save off the previous solution.
     178            0 :     if (refinement.save_adapt_iterations)
     179              :     {
     180            0 :       SaveIteration(comm, post_dir, it,
     181            0 :                     1 + static_cast<int>(std::log10(refinement.max_it)));
     182              :     }
     183              : 
     184              :     // Mark.
     185            0 :     const auto marked_elements = [&comm, &refinement](const auto &indicators)
     186              :     {
     187            0 :       const auto [threshold, marked_error] = utils::ComputeDorflerThreshold(
     188            0 :           comm, indicators.Local(), refinement.update_fraction);
     189            0 :       const auto marked_elements = MarkedElements(indicators.Local(), threshold);
     190              :       const auto [glob_marked_elements, glob_elements] =
     191            0 :           linalg::GlobalSize2(comm, marked_elements, indicators.Local());
     192            0 :       Mpi::Print(
     193              :           " Marked {:d}/{:d} elements for refinement ({:.2f}% of the error, θ = {:.2f})\n",
     194            0 :           glob_marked_elements, glob_elements, 100 * marked_error,
     195            0 :           refinement.update_fraction);
     196            0 :       return marked_elements;
     197            0 :     }(indicators);
     198              : 
     199              :     // Refine.
     200              :     {
     201              :       mfem::ParMesh &fine_mesh = *mesh.back();
     202            0 :       const auto initial_elem_count = fine_mesh.GetGlobalNE();
     203            0 :       fine_mesh.GeneralRefinement(marked_elements, -1, refinement.max_nc_levels);
     204            0 :       const auto final_elem_count = fine_mesh.GetGlobalNE();
     205            0 :       Mpi::Print(" {} mesh refinement added {:d} elements (initial = {:d}, final = {:d})\n",
     206            0 :                  fine_mesh.Nonconforming() ? "Nonconforming" : "Conforming",
     207            0 :                  final_elem_count - initial_elem_count, initial_elem_count,
     208              :                  final_elem_count);
     209              :     }
     210              : 
     211              :     // Optionally rebalance and write the adapted mesh to file.
     212              :     {
     213            0 :       const auto ratio_pre = mesh::RebalanceMesh(iodata, *mesh.back());
     214            0 :       if (ratio_pre > refinement.maximum_imbalance)
     215              :       {
     216              :         int min_elem, max_elem;
     217            0 :         min_elem = max_elem = mesh.back()->GetNE();
     218            0 :         Mpi::GlobalMin(1, &min_elem, comm);
     219            0 :         Mpi::GlobalMax(1, &max_elem, comm);
     220            0 :         const auto ratio_post = double(max_elem) / min_elem;
     221            0 :         Mpi::Print(" Rebalanced mesh: Ratio {:.3f} exceeded max. allowed value {:.3f} "
     222              :                    "(new ratio = {:.3f})\n",
     223            0 :                    ratio_pre, refinement.maximum_imbalance, ratio_post);
     224              :       }
     225            0 :       mesh.back()->Update();
     226              :     }
     227              : 
     228              :     // Solve + estimate.
     229            0 :     Mpi::Print("\nProceeding with solve/estimate iteration {}...\n", it + 1);
     230            0 :     std::tie(indicators, ntdof) = Solve(mesh);
     231            0 :     err = indicators.Norml2(comm);
     232            0 :   }
     233            0 :   Mpi::Print("\nCompleted {:d} iteration{} of adaptive mesh refinement (AMR):\n"
     234              :              " Indicator norm = {:.3e}, global unknowns = {:d}\n"
     235              :              " Max. iterations = {:d}, tol. = {:.3e}{}\n",
     236            0 :              it, (it == 1 ? "" : "s"), err, ntdof, refinement.max_it, refinement.tol,
     237              :              (refinement.max_size > 0
     238            0 :                   ? ", max. size = " + std::to_string(refinement.max_size)
     239            0 :                   : ""));
     240            0 : }
     241              : 
     242            0 : void BaseSolver::SaveMetadata(const FiniteElementSpaceHierarchy &fespaces) const
     243              : {
     244              :   const auto &fespace = fespaces.GetFinestFESpace();
     245            0 :   HYPRE_BigInt ne = fespace.GetParMesh().GetNE();
     246              :   Mpi::GlobalSum(1, &ne, fespace.GetComm());
     247            0 :   std::vector<HYPRE_BigInt> ndofs(fespaces.GetNumLevels());
     248            0 :   for (std::size_t l = 0; l < fespaces.GetNumLevels(); l++)
     249              :   {
     250            0 :     ndofs[l] = fespaces.GetFESpaceAtLevel(l).GlobalTrueVSize();
     251              :   }
     252            0 :   if (root)
     253              :   {
     254            0 :     json meta = LoadMetadata(post_dir);
     255            0 :     meta["Problem"]["MeshElements"] = ne;
     256            0 :     meta["Problem"]["DegreesOfFreedom"] = ndofs.back();
     257            0 :     meta["Problem"]["MultigridDegreesOfFreedom"] = ndofs;
     258            0 :     WriteMetadata(post_dir, meta);
     259              :   }
     260            0 : }
     261              : 
     262              : template <typename SolverType>
     263            0 : void BaseSolver::SaveMetadata(const SolverType &ksp) const
     264              : {
     265            0 :   if (root)
     266              :   {
     267            0 :     json meta = LoadMetadata(post_dir);
     268            0 :     meta["LinearSolver"]["TotalSolves"] = ksp.NumTotalMult();
     269            0 :     meta["LinearSolver"]["TotalIts"] = ksp.NumTotalMultIterations();
     270            0 :     WriteMetadata(post_dir, meta);
     271              :   }
     272            0 : }
     273              : 
     274            0 : void BaseSolver::SaveMetadata(const Timer &timer) const
     275              : {
     276            0 :   if (root)
     277              :   {
     278            0 :     json meta = LoadMetadata(post_dir);
     279            0 :     for (int i = Timer::INIT; i < Timer::NUM_TIMINGS; i++)
     280              :     {
     281            0 :       auto key = Timer::descriptions[i];
     282            0 :       key.erase(std::remove_if(key.begin(), key.end(), isspace), key.end());
     283            0 :       meta["ElapsedTime"]["Durations"][key] = timer.Data((Timer::Index)i);
     284            0 :       meta["ElapsedTime"]["Counts"][key] = timer.Counts((Timer::Index)i);
     285              :     }
     286            0 :     WriteMetadata(post_dir, meta);
     287              :   }
     288            0 : }
     289              : 
     290            0 : void BaseSolver::SaveMetadata(const PortExcitations &excitation_helper) const
     291              : {
     292            0 :   if (root)
     293              :   {
     294            0 :     nlohmann::json meta = LoadMetadata(post_dir);
     295            0 :     meta["Excitations"] = excitation_helper;
     296            0 :     WriteMetadata(post_dir, meta);
     297              :   }
     298            0 : }
     299              : 
     300              : template void BaseSolver::SaveMetadata<KspSolver>(const KspSolver &) const;
     301              : template void BaseSolver::SaveMetadata<ComplexKspSolver>(const ComplexKspSolver &) const;
     302              : 
     303              : }  // namespace palace
        

Generated by: LCOV version 2.0-1