LCOV - code coverage report
Current view: top level - src/neml2/userobjects - NEML2FEInterpolation.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 10 213 4.7 %
Date: 2026-05-29 20:35:17 Functions: 1 26 3.8 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #ifdef NEML2_ENABLED
      11             : 
      12             : // libmesh includes
      13             : #include "libmesh/petsc_vector.h"
      14             : 
      15             : // Torch includes
      16             : #include <ATen/ops/from_blob.h>
      17             : 
      18             : // NEML2 includes
      19             : #include "neml2/tensors/functions/discretization/scatter.h"
      20             : #include "neml2/tensors/functions/discretization/interpolate.h"
      21             : 
      22             : // MOOSE includes
      23             : #include "NEML2FEInterpolation.h"
      24             : 
      25             : using namespace libMesh;
      26             : 
      27             : registerMooseObject("MooseApp", NEML2FEInterpolation);
      28             : 
      29             : InputParameters
      30           2 : NEML2FEInterpolation::validParams()
      31             : {
      32           2 :   InputParameters params = ElementUserObject::validParams();
      33             : 
      34           4 :   params.addClassDescription(
      35             :       "This user object provides an interface to NEML2 for finite element "
      36             :       "interpolation of variables and their gradients. It gathers the shape "
      37             :       "functions and DOF maps for each variable in the assembly and provides "
      38             :       "them as NEML2 tensors for use in NEML2 models.");
      39             : 
      40           6 :   params.addRequiredParam<UserObjectName>(
      41             :       "assembly", "The NEML2Assembly object to use to provide assembly information");
      42             : 
      43           2 :   ExecFlagEnum execute_options = MooseUtils::getDefaultExecFlagEnum();
      44           6 :   execute_options = {EXEC_INITIAL, EXEC_LINEAR};
      45           4 :   params.set<ExecFlagEnum>("execute_on") = execute_options;
      46           2 :   params.suppressParameter<ExecFlagEnum>("execute_on");
      47             : 
      48           4 :   return params;
      49           4 : }
      50             : 
      51           0 : NEML2FEInterpolation::NEML2FEInterpolation(const InputParameters & parameters)
      52           0 :   : ElementUserObject(parameters), _neml2_assembly(getUserObject<NEML2Assembly>("assembly"))
      53             : {
      54           0 : }
      55             : 
      56             : const neml2::Tensor &
      57           0 : NEML2FEInterpolation::getValue(const std::string & var_name)
      58             : {
      59           0 :   auto [it, success] = _vars.emplace(var_name, neml2::Tensor());
      60             : 
      61           0 :   if (success)
      62             :   {
      63           0 :     const auto * var = getMOOSEVariable(var_name);
      64           0 :     _phis.emplace(var->feType(), &var->phi());
      65           0 :     _moose_vars.emplace(var_name, var);
      66             :   }
      67             : 
      68           0 :   return it->second;
      69             : }
      70             : 
      71             : const neml2::Tensor &
      72           0 : NEML2FEInterpolation::getGradient(const std::string & var_name)
      73             : {
      74           0 :   auto [it, success] = _grad_vars.emplace(var_name, neml2::Tensor());
      75             : 
      76           0 :   if (success)
      77             :   {
      78           0 :     const auto * var = getMOOSEVariable(var_name);
      79           0 :     _grad_phis.emplace(var->feType(), &var->gradPhi());
      80           0 :     _moose_vars.emplace(var_name, var);
      81             :   }
      82             : 
      83           0 :   return it->second;
      84             : }
      85             : 
      86             : const neml2::Tensor &
      87           0 : NEML2FEInterpolation::getPhi(const std::string & var_name)
      88             : {
      89           0 :   const auto * var = getMOOSEVariable(var_name);
      90             : 
      91           0 :   const auto it = _neml2_phi.find(var->feType());
      92           0 :   if (it != _neml2_phi.end())
      93           0 :     return it->second;
      94             : 
      95           0 :   _phis.emplace(var->feType(), &var->phi());
      96           0 :   auto [it2, success] = _neml2_phi.emplace(var->feType(), neml2::Tensor());
      97           0 :   return it2->second;
      98             : }
      99             : 
     100             : const neml2::Tensor &
     101           0 : NEML2FEInterpolation::getPhiGradient(const std::string & var_name)
     102             : {
     103           0 :   const auto * var = getMOOSEVariable(var_name);
     104             : 
     105           0 :   const auto it = _neml2_grad_phi.find(var->feType());
     106           0 :   if (it != _neml2_grad_phi.end())
     107           0 :     return it->second;
     108             : 
     109           0 :   _grad_phis.emplace(var->feType(), &var->gradPhi());
     110           0 :   auto [it2, success] = _neml2_grad_phi.emplace(var->feType(), neml2::Tensor());
     111           0 :   return it2->second;
     112             : }
     113             : 
     114             : const neml2::Tensor &
     115           0 : NEML2FEInterpolation::getDofMap(const std::string & var_name)
     116             : {
     117           0 :   return _neml2_dof_map[var_name];
     118             : }
     119             : 
     120             : const std::vector<dof_id_type> &
     121           0 : NEML2FEInterpolation::getGlobalDofMap(const std::string & var_name)
     122             : {
     123           0 :   return _moose_dof_map_global[var_name];
     124             : }
     125             : 
     126             : int64_t
     127           0 : NEML2FEInterpolation::local_ndof() const
     128             : {
     129           0 :   return _local_ndof;
     130             : }
     131             : 
     132             : const MooseVariableFE<Real> *
     133           0 : NEML2FEInterpolation::getMOOSEVariable(const std::string & var_name) const
     134             : {
     135           0 :   const auto * var = &_fe_problem.getVariable(
     136             :       0, var_name, Moose::VarKindType::VAR_SOLVER, Moose::VarFieldType::VAR_FIELD_STANDARD);
     137           0 :   const auto * var_fe = dynamic_cast<const MooseVariableFE<Real> *>(var);
     138             : 
     139           0 :   if (!var_fe)
     140           0 :     mooseError("NEML2FEInterpolation only supports variables of type MooseVariableFE<Real>");
     141             : 
     142           0 :   if (var_fe->scalingFactor() != 1)
     143           0 :     mooseError("Scaling factors other than unity are not yet supported");
     144             : 
     145             :   // check domain restrictions for compatibility
     146           0 :   if (!var_fe->hasBlocks(blockIDs()))
     147           0 :     mooseError("The variable '",
     148           0 :                var_fe->name(),
     149             :                "' must be defined on all blocks '",
     150           0 :                name(),
     151             :                "' is defined on.");
     152             : 
     153           0 :   return var_fe;
     154             : }
     155             : 
     156             : void
     157           0 : NEML2FEInterpolation::initialSetup()
     158             : {
     159           0 :   _petsc_solution = dynamic_cast<const PetscVector<Real> *>(_sys.currentSolution());
     160             : 
     161             :   // check if the solution vector is of a supported type
     162           0 :   if (!_petsc_solution)
     163           0 :     mooseError("Only solution vectors of type PetscVector are currently supported");
     164             : 
     165           0 :   if (_tid != 0)
     166           0 :     syncWithMainThread();
     167           0 : }
     168             : 
     169             : void
     170           0 : NEML2FEInterpolation::invalidateFEMContext()
     171             : {
     172           0 :   _fem_context_up_to_date = false;
     173           0 :   invalidateInterpolations();
     174           0 : }
     175             : 
     176             : void
     177           0 : NEML2FEInterpolation::invalidateInterpolations()
     178             : {
     179           0 :   _interp_up_to_date = false;
     180           0 : }
     181             : 
     182             : void
     183           0 : NEML2FEInterpolation::meshChanged()
     184             : {
     185           0 :   invalidateFEMContext();
     186           0 : }
     187             : 
     188             : void
     189           0 : NEML2FEInterpolation::initialize()
     190             : {
     191           0 :   if (_fem_context_up_to_date)
     192           0 :     return;
     193             : 
     194           0 :   _ndofe.clear();
     195           0 :   _moose_dof_map.clear();
     196           0 :   _moose_dof_map_global.clear();
     197           0 :   _moose_phi.clear();
     198           0 :   _moose_grad_phi.clear();
     199           0 :   _local_ndof = 0;
     200             : }
     201             : 
     202             : void
     203           0 : NEML2FEInterpolation::syncWithMainThread()
     204             : {
     205           0 :   auto & main_uo = _fe_problem.getUserObject<NEML2FEInterpolation>(name(), /*tid=*/0);
     206           0 :   for (const auto & [var_name, var] : main_uo._moose_vars)
     207             :   {
     208           0 :     _moose_vars.emplace(var_name, getMOOSEVariable(var_name));
     209           0 :     if (main_uo._phis.count(var->feType()))
     210           0 :       getPhi(var_name);
     211           0 :     if (main_uo._grad_phis.count(var->feType()))
     212           0 :       getPhiGradient(var_name);
     213             :   }
     214           0 : }
     215             : 
     216             : void
     217           0 : NEML2FEInterpolation::threadJoin(const UserObject & y)
     218             : {
     219           0 :   const auto & other = static_cast<const NEML2FEInterpolation &>(y);
     220             :   mooseAssert(_fem_context_up_to_date == other._fem_context_up_to_date,
     221             :               "NEML2FEInterpolation becomes out of sync with other thread");
     222             : 
     223           0 :   if (_fem_context_up_to_date)
     224           0 :     return;
     225             : 
     226           0 :   auto merge_map_vecs = [](auto & map1, const auto & map2)
     227             :   {
     228           0 :     for (const auto & [key, map2_val] : map2)
     229             :     {
     230           0 :       auto & map1_val = map1[key];
     231           0 :       map1_val.insert(map1_val.end(), map2_val.begin(), map2_val.end());
     232             :     }
     233           0 :   };
     234             : 
     235           0 :   merge_map_vecs(_moose_dof_map, other._moose_dof_map);
     236           0 :   merge_map_vecs(_moose_phi, other._moose_phi);
     237           0 :   merge_map_vecs(_moose_grad_phi, other._moose_grad_phi);
     238             : }
     239             : 
     240             : void
     241           0 : NEML2FEInterpolation::execute()
     242             : {
     243           0 :   if (_fem_context_up_to_date)
     244           0 :     return;
     245             : 
     246             :   // DOF indices
     247           0 :   const auto & nl_dof_map = _sys.dofMap();
     248           0 :   for (const auto & [var_name, var] : _moose_vars)
     249             :   {
     250           0 :     nl_dof_map.dof_indices(_current_elem, _dof_indices, var->number());
     251           0 :     auto [it, success] = _ndofe.emplace(var->feType(), _dof_indices.size());
     252           0 :     if (!success && std::size_t(it->second) != _dof_indices.size())
     253           0 :       mooseError("DOF map size mismatch for variable ",
     254             :                  var_name,
     255             :                  ", got ",
     256           0 :                  it->second,
     257             :                  " and ",
     258           0 :                  _dof_indices.size());
     259           0 :     auto & moose_dof_map = _moose_dof_map[var_name];
     260           0 :     auto & moose_dof_map_global = _moose_dof_map_global[var_name];
     261           0 :     for (auto dof : _dof_indices)
     262             :     {
     263           0 :       moose_dof_map.push_back(_petsc_solution->map_global_to_local_index(dof));
     264           0 :       moose_dof_map_global.push_back(dof);
     265             :     }
     266             :   }
     267             : 
     268             :   // shape function values
     269           0 :   for (const auto & [fetype, phi] : _phis)
     270             :   {
     271           0 :     auto & moose_phi = _moose_phi[fetype];
     272           0 :     for (auto i : index_range(*phi))
     273           0 :       for (auto qp : index_range(_q_point))
     274           0 :         moose_phi.push_back((*phi)[i][qp]);
     275             :   }
     276             : 
     277             :   // shape function gradients
     278           0 :   for (const auto & [fetype, grad_phi] : _grad_phis)
     279             :   {
     280           0 :     auto & moose_grad_phi = _moose_grad_phi[fetype];
     281           0 :     for (auto i : index_range(*grad_phi))
     282           0 :       for (auto qp : index_range(_q_point))
     283           0 :         for (auto j : make_range(3))
     284           0 :           moose_grad_phi.push_back((*grad_phi)[i][qp](j));
     285             :   }
     286             : }
     287             : 
     288             : void
     289           0 : NEML2FEInterpolation::finalize()
     290             : {
     291           0 :   TIME_SECTION("finalize", 1, "Updating FEM context and interpolations for NEML2");
     292             : 
     293           0 :   if (!_fem_context_up_to_date)
     294           0 :     updateFEMContext();
     295             : 
     296           0 :   if (!_interp_up_to_date)
     297           0 :     updateInterpolations();
     298           0 : }
     299             : 
     300             : void
     301           0 : NEML2FEInterpolation::updateFEMContext()
     302             : {
     303           0 :   TIME_SECTION("updateFEMContext", 2, "Updating FEM context for NEML2");
     304             : 
     305           0 :   updateDofMap();
     306           0 :   updatePhi();
     307           0 :   updateGradPhi();
     308             : 
     309             :   // done
     310           0 :   _fem_context_up_to_date = true;
     311           0 : }
     312             : 
     313             : void
     314           0 : NEML2FEInterpolation::updateDofMap()
     315             : {
     316           0 :   auto device = _app.getLibtorchDevice();
     317           0 :   auto nelem = _neml2_assembly.numElem();
     318             : 
     319           0 :   for (auto & [var_name, moose_dof_map] : _moose_dof_map)
     320             :   {
     321           0 :     auto var = _moose_vars.at(var_name);
     322           0 :     auto ndofe = _ndofe.at(var->feType());
     323             : 
     324             :     // sanity check on sizes
     325           0 :     if (moose_dof_map.size() != std::size_t(nelem * ndofe))
     326           0 :       mooseError(
     327           0 :           "dof map size mismatch, expected ", nelem * ndofe, " but got ", moose_dof_map.size());
     328             : 
     329             :     // convert to neml2 tensor
     330           0 :     _neml2_dof_map[var_name] =
     331           0 :         neml2::Tensor(at::from_blob(moose_dof_map.data(), {nelem, ndofe}, torch::kInt64), 2)
     332           0 :             .to(device);
     333             : 
     334           0 :     _local_ndof = std::max(_local_ndof, _neml2_dof_map[var_name].max().item<int64_t>() + 1);
     335             :   }
     336           0 : }
     337             : 
     338             : void
     339           0 : NEML2FEInterpolation::updatePhi()
     340             : {
     341           0 :   auto device = _app.getLibtorchDevice();
     342           0 :   auto nelem = _neml2_assembly.numElem();
     343           0 :   auto nqp = _neml2_assembly.numQP();
     344             : 
     345           0 :   for (auto & [fetype, moose_phi] : _moose_phi)
     346             :   {
     347           0 :     auto ndofe = _ndofe.at(fetype);
     348             : 
     349             :     // sanity check on sizes
     350           0 :     if (moose_phi.size() != std::size_t(nelem * ndofe * nqp))
     351           0 :       mooseError("shape function size mismatch, expected ",
     352           0 :                  nelem * ndofe * nqp,
     353             :                  " but got ",
     354           0 :                  moose_phi.size());
     355           0 :     _neml2_phi[fetype] =
     356           0 :         neml2::Tensor(at::from_blob(moose_phi.data(), {nelem, ndofe, nqp}, torch::kFloat64), 3)
     357           0 :             .to(device);
     358             :   }
     359           0 : }
     360             : 
     361             : void
     362           0 : NEML2FEInterpolation::updateGradPhi()
     363             : {
     364           0 :   auto device = _app.getLibtorchDevice();
     365           0 :   auto nelem = _neml2_assembly.numElem();
     366           0 :   auto nqp = _neml2_assembly.numQP();
     367             : 
     368           0 :   for (auto & [fetype, moose_grad_phi] : _moose_grad_phi)
     369             :   {
     370           0 :     auto ndofe = _ndofe.at(fetype);
     371             : 
     372             :     // sanity check on sizes
     373           0 :     if (moose_grad_phi.size() != std::size_t(nelem * ndofe * nqp * 3))
     374           0 :       mooseError("shape function gradient size mismatch, expected ",
     375           0 :                  nelem * ndofe * nqp * 3,
     376             :                  " but got ",
     377           0 :                  moose_grad_phi.size());
     378           0 :     _neml2_grad_phi[fetype] =
     379           0 :         neml2::Tensor(at::from_blob(moose_grad_phi.data(), {nelem, ndofe, nqp, 3}, torch::kFloat64),
     380             :                       3)
     381           0 :             .to(device);
     382             :   }
     383           0 : }
     384             : 
     385             : void
     386           0 : NEML2FEInterpolation::updateInterpolations()
     387             : {
     388           0 :   TIME_SECTION("updateInterpolations", 2, "Updating FEM interpolations for NEML2");
     389             : 
     390             :   // convert the local solution vector to neml2 tensor
     391           0 :   auto sol = at::from_blob(const_cast<Real *>(_petsc_solution->get_array_read()),
     392           0 :                            {local_ndof()},
     393             :                            torch::kFloat64)
     394           0 :                  .to(_app.getLibtorchDevice());
     395             : 
     396             :   // interpolate variable values
     397           0 :   for (auto & [var_name, val] : _vars)
     398             :   {
     399           0 :     const auto & dof_map = _neml2_dof_map[var_name];
     400           0 :     const auto fetype = _moose_vars[var_name]->feType();
     401           0 :     const auto & phi = _neml2_phi[fetype];
     402           0 :     auto sol_scattered = neml2::discretization::scatter(sol, dof_map);
     403           0 :     val = neml2::discretization::interpolate(sol_scattered, phi);
     404           0 :   }
     405             : 
     406             :   // interpolate variable gradients
     407           0 :   for (auto & [var_name, val] : _grad_vars)
     408             :   {
     409           0 :     const auto & dof_map = _neml2_dof_map[var_name];
     410           0 :     const auto fetype = _moose_vars[var_name]->feType();
     411           0 :     const auto & grad_phi = _neml2_grad_phi[fetype];
     412           0 :     auto sol_scattered = neml2::discretization::scatter(sol, dof_map);
     413           0 :     val = neml2::discretization::interpolate(sol_scattered, grad_phi);
     414           0 :   }
     415             : 
     416             :   // close solution and residual vector access
     417           0 :   const_cast<PetscVector<Real> *>(_petsc_solution)->restore_array();
     418             : 
     419             :   // done
     420           0 :   _interp_up_to_date = true;
     421           0 : }
     422             : 
     423             : #endif

Generated by: LCOV version 1.14