LCOV - code coverage report
Current view: top level - drivers - electrostaticsolver.cpp (source / functions) Coverage Total Hit
Test: Palace Coverage Report Lines: 0.0 % 80 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 "electrostaticsolver.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/laplaceoperator.hpp"
      13              : #include "models/postoperator.hpp"
      14              : #include "utils/communication.hpp"
      15              : #include "utils/iodata.hpp"
      16              : #include "utils/timer.hpp"
      17              : 
      18              : namespace palace
      19              : {
      20              : 
      21              : std::pair<ErrorIndicator, long long int>
      22            0 : ElectrostaticSolver::Solve(const std::vector<std::unique_ptr<Mesh>> &mesh) const
      23              : {
      24              :   // Construct the system matrix defining the linear operator. Dirichlet boundaries are
      25              :   // handled eliminating the rows and columns of the system matrix for the corresponding
      26              :   // dofs. The eliminated matrix is stored in order to construct the RHS vector for nonzero
      27              :   // prescribed BC values.
      28            0 :   BlockTimer bt0(Timer::CONSTRUCT);
      29            0 :   LaplaceOperator laplace_op(iodata, mesh);
      30            0 :   auto K = laplace_op.GetStiffnessMatrix();
      31              :   const auto &Grad = laplace_op.GetGradMatrix();
      32            0 :   SaveMetadata(laplace_op.GetH1Spaces());
      33              : 
      34              :   // Set up the linear solver.
      35            0 :   KspSolver ksp(iodata, laplace_op.GetH1Spaces());
      36            0 :   ksp.SetOperators(*K, *K);
      37              : 
      38              :   // Terminal indices are the set of boundaries over which to compute the capacitance
      39              :   // matrix. Terminal boundaries are aliases for ports.
      40            0 :   PostOperator<ProblemType::ELECTROSTATIC> post_op(iodata, laplace_op);
      41            0 :   int n_step = static_cast<int>(laplace_op.GetSources().size());
      42            0 :   MFEM_VERIFY(n_step > 0, "No terminal boundaries specified for electrostatic simulation!");
      43              : 
      44              :   // Right-hand side term and solution vector storage.
      45              :   Vector RHS(Grad.Width()), E(Grad.Height());
      46            0 :   std::vector<Vector> V(n_step);
      47              : 
      48              :   // Initialize structures for storing and reducing the results of error estimation.
      49              :   GradFluxErrorEstimator estimator(
      50              :       laplace_op.GetMaterialOp(), laplace_op.GetNDSpace(), laplace_op.GetRTSpaces(),
      51            0 :       iodata.solver.linear.estimator_tol, iodata.solver.linear.estimator_max_it, 0,
      52            0 :       iodata.solver.linear.estimator_mg);
      53              :   ErrorIndicator indicator;
      54              : 
      55              :   // Main loop over terminal boundaries.
      56            0 :   Mpi::Print("\nComputing electrostatic fields for {:d} terminal {}\n", n_step,
      57            0 :              (n_step > 1) ? "boundaries" : "boundary");
      58              :   int step = 0;
      59              :   auto t0 = Timer::Now();
      60            0 :   for (const auto &[idx, data] : laplace_op.GetSources())
      61              :   {
      62            0 :     Mpi::Print("\nIt {:d}/{:d}: Index = {:d} (elapsed time = {:.2e} s)\n", step + 1, n_step,
      63            0 :                idx, Timer::Duration(Timer::Now() - t0).count());
      64              : 
      65              :     // Form and solve the linear system for a prescribed nonzero voltage on the specified
      66              :     // terminal.
      67            0 :     Mpi::Print("\n");
      68            0 :     laplace_op.GetExcitationVector(idx, *K, V[step], RHS);
      69            0 :     ksp.Mult(RHS, V[step]);
      70              : 
      71              :     // Start Post-processing.
      72            0 :     BlockTimer bt2(Timer::POSTPRO);
      73            0 :     Mpi::Print(" Sol. ||V|| = {:.6e} (||RHS|| = {:.6e})\n",
      74            0 :                linalg::Norml2(laplace_op.GetComm(), V[step]),
      75            0 :                linalg::Norml2(laplace_op.GetComm(), RHS));
      76              : 
      77              :     // Compute E = -∇V on the true dofs.
      78            0 :     E = 0.0;
      79            0 :     Grad.AddMult(V[step], E, -1.0);
      80              : 
      81              :     // Measurement and printing.
      82            0 :     auto total_domain_energy = post_op.MeasureAndPrintAll(step, V[step], E, idx);
      83              : 
      84              :     // Calculate and record the error indicators.
      85            0 :     Mpi::Print(" Updating solution error estimates\n");
      86            0 :     estimator.AddErrorIndicator(E, total_domain_energy, indicator);
      87              : 
      88              :     // Next terminal.
      89              :     step++;
      90            0 :   }
      91              : 
      92              :   // Postprocess the capacitance matrix from the computed field solutions.
      93            0 :   BlockTimer bt1(Timer::POSTPRO);
      94            0 :   SaveMetadata(ksp);
      95            0 :   PostprocessTerminals(post_op, laplace_op.GetSources(), V);
      96            0 :   post_op.MeasureFinalize(indicator);
      97            0 :   return {indicator, laplace_op.GlobalTrueVSize()};
      98            0 : }
      99              : 
     100            0 : void ElectrostaticSolver::PostprocessTerminals(
     101              :     PostOperator<ProblemType::ELECTROSTATIC> &post_op,
     102              :     const std::map<int, mfem::Array<int>> &terminal_sources,
     103              :     const std::vector<Vector> &V) const
     104              : {
     105              :   // Postprocess the Maxwell capacitance matrix. See p. 97 of the COMSOL AC/DC Module manual
     106              :   // for the associated formulas based on the electric field energy based on a unit voltage
     107              :   // excitation for each terminal. Alternatively, we could compute the resulting terminal
     108              :   // charges from the prescribed voltage to get C directly as:
     109              :   //         Q_i = ∫ ρ dV = ∫ ∇ ⋅ (ε E) dV = ∫ (ε E) ⋅ n dS
     110              :   // and C_ij = Q_i/V_j. The energy formulation avoids having to locally integrate E = -∇V.
     111            0 :   mfem::DenseMatrix C(V.size()), Cm(V.size());
     112            0 :   for (int i = 0; i < C.Height(); i++)
     113              :   {
     114              :     // Diagonal: Cᵢᵢ = 2 Uₑ(Vᵢ) / Vᵢ² = (Vᵢᵀ K Vᵢ) / Vᵢ² (with ∀i, Vᵢ = 1)
     115              :     auto &V_gf = post_op.GetVGridFunction().Real();
     116            0 :     auto &D_gf = post_op.GetDomainPostOp().D;
     117            0 :     V_gf.SetFromTrueDofs(V[i]);
     118            0 :     post_op.GetDomainPostOp().M_elec->Mult(V_gf, D_gf);
     119            0 :     C(i, i) = Cm(i, i) = linalg::Dot<Vector>(post_op.GetComm(), V_gf, D_gf);
     120              : 
     121              :     // Off-diagonals: Cᵢⱼ = Uₑ(Vᵢ + Vⱼ) / (Vᵢ Vⱼ) - 1/2 (Vᵢ/Vⱼ Cᵢᵢ + Vⱼ/Vᵢ Cⱼⱼ)
     122              :     //                    = (Vⱼᵀ K Vᵢ) / (Vᵢ Vⱼ)
     123            0 :     for (int j = i + 1; j < C.Width(); j++)
     124              :     {
     125            0 :       V_gf.SetFromTrueDofs(V[j]);
     126            0 :       C(i, j) = linalg::Dot<Vector>(post_op.GetComm(), V_gf, D_gf);
     127            0 :       Cm(i, j) = -C(i, j);
     128            0 :       Cm(i, i) -= Cm(i, j);
     129              :     }
     130              : 
     131              :     // Copy lower triangle from already computed upper triangle.
     132            0 :     for (int j = 0; j < i; j++)
     133              :     {
     134            0 :       C(i, j) = C(j, i);
     135            0 :       Cm(i, j) = Cm(j, i);
     136            0 :       Cm(i, i) -= Cm(i, j);
     137              :     }
     138              :   }
     139            0 :   mfem::DenseMatrix Cinv(C);
     140            0 :   Cinv.Invert();  // In-place, uses LAPACK (when available) and should be cheap
     141              : 
     142              :   // Only root writes to disk (every process has full matrices).
     143            0 :   if (!root)
     144              :   {
     145              :     return;
     146              :   }
     147              :   using VT = Units::ValueType;
     148              :   using fmt::format;
     149              : 
     150              :   // Write capacitance matrix data.
     151            0 :   auto PrintMatrix = [&terminal_sources, this](const std::string &file,
     152              :                                                const std::string &name,
     153              :                                                const std::string &unit,
     154              :                                                const mfem::DenseMatrix &mat, double scale)
     155              :   {
     156            0 :     TableWithCSVFile output(post_dir / file);
     157            0 :     output.table.insert(Column("i", "i", 0, 0, 2, ""));
     158              :     int j = 0;
     159            0 :     for (const auto &[idx2, data2] : terminal_sources)
     160              :     {
     161            0 :       output.table.insert(format("i2{}", idx2), format("{}[i][{}] {}", name, idx2, unit));
     162              :       // Use the fact that iterator over i and j is the same span.
     163            0 :       output.table["i"] << idx2;
     164              : 
     165            0 :       auto &col = output.table[format("i2{}", idx2)];
     166            0 :       for (std::size_t i = 0; i < terminal_sources.size(); i++)
     167              :       {
     168            0 :         col << mat(i, j) * scale;
     169              :       }
     170            0 :       j++;
     171              :     }
     172            0 :     output.WriteFullTableTrunc();
     173            0 :   };
     174            0 :   const double F = iodata.units.Dimensionalize<VT::CAPACITANCE>(1.0);
     175            0 :   PrintMatrix("terminal-C.csv", "C", "(F)", C, F);
     176            0 :   PrintMatrix("terminal-Cinv.csv", "C⁻¹", "(1/F)", Cinv, 1.0 / F);
     177            0 :   PrintMatrix("terminal-Cm.csv", "C_m", "(F)", Cm, F);
     178              : 
     179              :   // Also write out a file with terminal voltage excitations.
     180              :   {
     181            0 :     TableWithCSVFile terminal_V(post_dir / "terminal-V.csv");
     182            0 :     terminal_V.table.insert(Column("i", "i", 0, 0, 2, ""));
     183            0 :     terminal_V.table.insert("Vinc", "V_inc[i] (V)");
     184            0 :     for (const auto &[idx, data] : terminal_sources)
     185              :     {
     186            0 :       terminal_V.table["i"] << double(idx);
     187            0 :       terminal_V.table["Vinc"] << iodata.units.Dimensionalize<VT::VOLTAGE>(1.0);
     188              :     }
     189            0 :     terminal_V.WriteFullTableTrunc();
     190              :   }
     191            0 : }
     192              : 
     193              : }  // namespace palace
        

Generated by: LCOV version 2.0-1