LCOV - code coverage report
Current view: top level - drivers - magnetostaticsolver.cpp (source / functions) Coverage Total Hit
Test: Palace Coverage Report Lines: 0.0 % 87 0
Test Date: 2025-10-23 22:45:05 Functions: 0.0 % 3 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 "magnetostaticsolver.hpp"
       5              : 
       6              : #include <mfem.hpp>
       7              : #include "fem/errorindicator.hpp"
       8              : #include "fem/mesh.hpp"
       9              : #include "linalg/errorestimator.hpp"
      10              : #include "linalg/ksp.hpp"
      11              : #include "linalg/operator.hpp"
      12              : #include "models/curlcurloperator.hpp"
      13              : #include "models/postoperator.hpp"
      14              : #include "models/surfacecurrentoperator.hpp"
      15              : #include "utils/communication.hpp"
      16              : #include "utils/iodata.hpp"
      17              : #include "utils/timer.hpp"
      18              : 
      19              : namespace palace
      20              : {
      21              : 
      22              : std::pair<ErrorIndicator, long long int>
      23            0 : MagnetostaticSolver::Solve(const std::vector<std::unique_ptr<Mesh>> &mesh) const
      24              : {
      25              :   // Construct the system matrix defining the linear operator. Dirichlet boundaries are
      26              :   // handled eliminating the rows and columns of the system matrix for the corresponding
      27              :   // dofs.
      28            0 :   BlockTimer bt0(Timer::CONSTRUCT);
      29            0 :   CurlCurlOperator curlcurl_op(iodata, mesh);
      30            0 :   auto K = curlcurl_op.GetStiffnessMatrix();
      31              :   const auto &Curl = curlcurl_op.GetCurlMatrix();
      32            0 :   SaveMetadata(curlcurl_op.GetNDSpaces());
      33              : 
      34              :   // Set up the linear solver.
      35            0 :   KspSolver ksp(iodata, curlcurl_op.GetNDSpaces(), &curlcurl_op.GetH1Spaces());
      36            0 :   ksp.SetOperators(*K, *K);
      37              : 
      38              :   // Terminal indices are the set of boundaries over which to compute the inductance matrix.
      39            0 :   PostOperator<ProblemType::MAGNETOSTATIC> post_op(iodata, curlcurl_op);
      40            0 :   int n_step = static_cast<int>(curlcurl_op.GetSurfaceCurrentOp().Size());
      41            0 :   MFEM_VERIFY(n_step > 0,
      42              :               "No surface current boundaries specified for magnetostatic simulation!");
      43              : 
      44              :   // Source term and solution vector storage.
      45              :   Vector RHS(Curl.Width()), B(Curl.Height());
      46            0 :   std::vector<Vector> A(n_step);
      47            0 :   std::vector<double> I_inc(n_step);
      48              : 
      49              :   // Initialize structures for storing and reducing the results of error estimation.
      50              :   CurlFluxErrorEstimator estimator(
      51              :       curlcurl_op.GetMaterialOp(), curlcurl_op.GetRTSpace(), curlcurl_op.GetNDSpaces(),
      52            0 :       iodata.solver.linear.estimator_tol, iodata.solver.linear.estimator_max_it, 0,
      53            0 :       iodata.solver.linear.estimator_mg);
      54              :   ErrorIndicator indicator;
      55              : 
      56              :   // Main loop over current source boundaries.
      57            0 :   Mpi::Print("\nComputing magnetostatic fields for {:d} source {}\n", n_step,
      58            0 :              (n_step > 1) ? "boundaries" : "boundary");
      59              :   int step = 0;
      60              :   auto t0 = Timer::Now();
      61            0 :   for (const auto &[idx, data] : curlcurl_op.GetSurfaceCurrentOp())
      62              :   {
      63            0 :     Mpi::Print("\nIt {:d}/{:d}: Index = {:d} (elapsed time = {:.2e} s)\n", step + 1, n_step,
      64            0 :                idx, Timer::Duration(Timer::Now() - t0).count());
      65              : 
      66              :     // Form and solve the linear system for a prescribed current on the specified source.
      67            0 :     Mpi::Print("\n");
      68            0 :     A[step].SetSize(RHS.Size());
      69            0 :     A[step].UseDevice(true);
      70            0 :     A[step] = 0.0;
      71            0 :     curlcurl_op.GetExcitationVector(idx, RHS);
      72            0 :     ksp.Mult(RHS, A[step]);
      73              : 
      74              :     // Start Post-processing.
      75            0 :     BlockTimer bt2(Timer::POSTPRO);
      76            0 :     Mpi::Print(" Sol. ||A|| = {:.6e} (||RHS|| = {:.6e})\n",
      77            0 :                linalg::Norml2(curlcurl_op.GetComm(), A[step]),
      78            0 :                linalg::Norml2(curlcurl_op.GetComm(), RHS));
      79              : 
      80              :     // Compute B = ∇ x A on the true dofs.
      81            0 :     Curl.Mult(A[step], B);
      82              : 
      83              :     // Save excitation current for inductance matrix calculation.
      84            0 :     I_inc[step] = data.GetExcitationCurrent();
      85              : 
      86              :     // Measurement and printing.
      87            0 :     auto total_domain_energy = post_op.MeasureAndPrintAll(step, A[step], B, idx);
      88              : 
      89              :     // Calculate and record the error indicators.
      90            0 :     Mpi::Print(" Updating solution error estimates\n");
      91            0 :     estimator.AddErrorIndicator(B, total_domain_energy, indicator);
      92              : 
      93              :     // Next source.
      94              :     step++;
      95            0 :   }
      96              : 
      97              :   // Postprocess the inductance matrix from the computed field solutions.
      98            0 :   BlockTimer bt1(Timer::POSTPRO);
      99            0 :   SaveMetadata(ksp);
     100            0 :   PostprocessTerminals(post_op, curlcurl_op.GetSurfaceCurrentOp(), A, I_inc);
     101            0 :   post_op.MeasureFinalize(indicator);
     102            0 :   return {indicator, curlcurl_op.GlobalTrueVSize()};
     103            0 : }
     104              : 
     105            0 : void MagnetostaticSolver::PostprocessTerminals(
     106              :     PostOperator<ProblemType::MAGNETOSTATIC> &post_op,
     107              :     const SurfaceCurrentOperator &surf_j_op, const std::vector<Vector> &A,
     108              :     const std::vector<double> &I_inc) const
     109              : {
     110              :   // Postprocess the Maxwell inductance matrix. See p. 97 of the COMSOL AC/DC Module manual
     111              :   // for the associated formulas based on the magnetic field energy based on a current
     112              :   // excitation for each port. Alternatively, we could compute the resulting loop fluxes to
     113              :   // get M directly as:
     114              :   //                         Φ_i = ∫ B ⋅ n_j dS
     115              :   // and M_ij = Φ_i/I_j. The energy formulation avoids having to locally integrate B =
     116              :   // ∇ x A.
     117            0 :   mfem::DenseMatrix M(A.size()), Mm(A.size());
     118            0 :   for (int i = 0; i < M.Height(); i++)
     119              :   {
     120              :     // Diagonal: Mᵢᵢ = 2 Uₘ(Aᵢ) / Iᵢ² = (Aᵢᵀ K Aᵢ) / Iᵢ²
     121              :     auto &A_gf = post_op.GetAGridFunction().Real();
     122            0 :     auto &H_gf = post_op.GetDomainPostOp().H;
     123            0 :     A_gf.SetFromTrueDofs(A[i]);
     124            0 :     post_op.GetDomainPostOp().M_mag->Mult(A_gf, H_gf);
     125            0 :     M(i, i) = Mm(i, i) =
     126            0 :         linalg::Dot<Vector>(post_op.GetComm(), A_gf, H_gf) / (I_inc[i] * I_inc[i]);
     127              : 
     128              :     // Off-diagonals: Mᵢⱼ = Uₘ(Aᵢ + Aⱼ) / (Iᵢ Iⱼ) - 1/2 (Iᵢ/Iⱼ Mᵢᵢ + Iⱼ/Iᵢ Mⱼⱼ)
     129              :     //                    = (Aⱼᵀ K Aᵢ) / (Iᵢ Iⱼ)
     130            0 :     for (int j = i + 1; j < M.Width(); j++)
     131              :     {
     132            0 :       A_gf.SetFromTrueDofs(A[j]);
     133            0 :       M(i, j) = linalg::Dot<Vector>(post_op.GetComm(), A_gf, H_gf) / (I_inc[i] * I_inc[j]);
     134            0 :       Mm(i, j) = -M(i, j);
     135            0 :       Mm(i, i) -= Mm(i, j);
     136              :     }
     137              : 
     138              :     // Copy lower triangle from already computed upper triangle.
     139            0 :     for (int j = 0; j < i; j++)
     140              :     {
     141            0 :       M(i, j) = M(j, i);
     142            0 :       Mm(i, j) = Mm(j, i);
     143            0 :       Mm(i, i) -= Mm(i, j);
     144              :     }
     145              :   }
     146            0 :   mfem::DenseMatrix Minv(M);
     147            0 :   Minv.Invert();  // In-place, uses LAPACK (when available) and should be cheap
     148              : 
     149              :   // Only root writes to disk (every process has full matrices).
     150            0 :   if (!root)
     151              :   {
     152              :     return;
     153              :   }
     154              :   using fmt::format;
     155              : 
     156              :   // Write inductance matrix data.
     157            0 :   auto PrintMatrix = [&surf_j_op, this](const std::string &file, const std::string &name,
     158              :                                         const std::string &unit,
     159              :                                         const mfem::DenseMatrix &mat, double scale)
     160              :   {
     161            0 :     TableWithCSVFile output(post_dir / file);
     162            0 :     output.table.insert(Column("i", "i", 0, 0, 2, ""));
     163              :     int j = 0;
     164            0 :     for (const auto &[idx2, data2] : surf_j_op)
     165              :     {
     166            0 :       output.table.insert(format("i2{}", idx2), format("{}[i][{}] {}", name, idx2, unit));
     167              :       // Use the fact that iterator over i and j is the same span.
     168            0 :       output.table["i"] << idx2;
     169              : 
     170            0 :       auto &col = output.table[format("i2{}", idx2)];
     171            0 :       for (std::size_t i = 0; i < surf_j_op.Size(); i++)
     172              :       {
     173            0 :         col << mat(i, j) * scale;
     174              :       }
     175            0 :       j++;
     176              :     }
     177            0 :     output.WriteFullTableTrunc();
     178            0 :   };
     179            0 :   const double H = iodata.units.GetScaleFactor<Units::ValueType::INDUCTANCE>();
     180            0 :   PrintMatrix("terminal-M.csv", "M", "(H)", M, H);
     181            0 :   PrintMatrix("terminal-Minv.csv", "M⁻¹", "(1/H)", Minv, 1.0 / H);
     182            0 :   PrintMatrix("terminal-Mm.csv", "M_m", "(H)", Mm, H);
     183              : 
     184              :   // Also write out a file with source current excitations.
     185              :   {
     186            0 :     TableWithCSVFile terminal_I(post_dir / "terminal-I.csv");
     187            0 :     terminal_I.table.insert(Column("i", "i", 0, 0, 2, ""));
     188            0 :     terminal_I.table.insert("Iinc", "I_inc[i] (A)");
     189              :     int i = 0;
     190            0 :     for (const auto &[idx, data] : surf_j_op)
     191              :     {
     192            0 :       terminal_I.table["i"] << double(idx);
     193            0 :       terminal_I.table["Iinc"] << iodata.units.Dimensionalize<Units::ValueType::CURRENT>(
     194            0 :           I_inc[i]);
     195            0 :       i++;
     196              :     }
     197            0 :     terminal_I.WriteFullTableTrunc();
     198              :   }
     199            0 : }
     200              : 
     201              : }  // namespace palace
        

Generated by: LCOV version 2.0-1