https://mooseframework.inl.gov
ModularGapConductanceConstraint.C
Go to the documentation of this file.
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 
11 #include "GapFluxModelBase.h"
14 
15 #include "MooseError.h"
16 
17 #include "libmesh/parallel_algebra.h"
18 
20 
23 {
25  params.addClassDescription(
26  "Computes the residual and Jacobian contributions for the 'Lagrange Multiplier' "
27  "implementation of the thermal contact problem. For more information, see the "
28  "detailed description here: http://tinyurl.com/gmmhbe9");
29  params.addParam<std::vector<UserObjectName>>("gap_flux_models",
30  "List of GapFluxModel user objects");
31  params.addCoupledVar("displacements", "Displacement variables");
32 
33  MooseEnum gap_geometry_type("AUTO PLATE CYLINDER SPHERE", "AUTO");
34  params.addParam<MooseEnum>(
35  "gap_geometry_type",
36  gap_geometry_type,
37  "Gap calculation type. The geometry type is used to compute "
38  "gap distances and scale fluxes to ensure energy balance. If AUTO is selected, the gap "
39  "geometry is automatically set via the mesh coordinate system.");
40  params.addRangeCheckedParam<Real>("max_gap", 1.0e6, "max_gap>=0", "A maximum gap size");
41  params.addParam<RealVectorValue>("cylinder_axis_point_1",
42  "Start point for line defining cylindrical axis");
43  params.addParam<RealVectorValue>("cylinder_axis_point_2",
44  "End point for line defining cylindrical axis");
45  params.addParam<RealVectorValue>("sphere_origin", "Origin for sphere geometry");
46 
47  // We should default use_displaced_mesh to true. If no displaced mesh exists
48  // FEProblemBase::addConstraint will automatically correct it to false. However,
49  // this will still prompt a call from AugmentSparsityOnInterface to get a displaced
50  // mortar interface since object._use_displaced_mesh = true.
51 
52  // These parameter groups have to match the MortarGapHeatTransferAction's
53  params.addParamNamesToGroup("gap_flux_models", "Gap flux models");
54  params.addParamNamesToGroup(
55  "gap_geometry_type max_gap cylinder_axis_point_1 cylinder_axis_point_2 sphere_origin",
56  "Gap geometry");
57 
58  return params;
59 }
60 
62  : ADMortarConstraint(parameters),
63  _gap_flux_model_names(getParam<std::vector<UserObjectName>>("gap_flux_models")),
64  _disp_name(parameters.getVecMooseType("displacements")),
65  _n_disp(_disp_name.size()),
66  _disp_secondary(_n_disp),
67  _disp_primary(_n_disp),
68  _gap_geometry_type(getParam<MooseEnum>("gap_geometry_type").getEnum<GapGeometry>()),
69  _gap_width(0.0),
70  _surface_integration_factor(1.0),
71  _p1(declareRestartableData<Point>("cylinder_axis_point_1", Point(0, 1, 0))),
72  _p2(declareRestartableData<Point>("cylinder_axis_point_2", Point(0, 0, 0))),
73  _r1(0),
74  _r2(0),
75  _max_gap(getParam<Real>("max_gap")),
76  _adjusted_length(0.0),
77  _disp_x_var(getVar("displacements", 0)),
78  _disp_y_var(getVar("displacements", 1)),
79  _disp_z_var(_n_disp == 3 ? getVar("displacements", 2) : nullptr)
80 {
81  if (_n_disp && !getParam<bool>("use_displaced_mesh"))
82  paramWarning("displacements",
83  "You are coupling displacement variables but are evaluating the gap width on the "
84  "undisplaced mesh. This is probably a mistake.");
85 
86  for (unsigned int i = 0; i < _n_disp; ++i)
87  {
88  auto & disp_var = _subproblem.getStandardVariable(_tid, _disp_name[i]);
89  _disp_secondary[i] = &disp_var.adSln();
90  _disp_primary[i] = &disp_var.adSlnNeighbor();
91  }
92 
93  const auto use_displaced_mesh = getParam<bool>("use_displaced_mesh");
94 
95  for (const auto & name : _gap_flux_model_names)
96  {
97  const auto & gap_model = getUserObjectByName<GapFluxModelBase>(name);
98 
99  // This constraint explicitly calls the gap flux model user objects to
100  // obtain contributions to its residuals. It therefore depends on all
101  // variables and material properties, that these gap flux models use, to be
102  // computed and up to date. To ensure that we collect all variable and
103  // material property dependencies of these models and declare them as
104  // dependencies of this constraint object. This turns an implicit, hidden
105  // dependency into an explicit dependency that MOOSE will automatically fulfill.
106 
107  // pass variable dependencies through
108  const auto & var_dependencies = gap_model.getMooseVariableDependencies();
109  for (const auto & var : var_dependencies)
111 
112  // pass material property dependencies through
113  const auto & mat_dependencies = gap_model.getMatPropDependencies();
114  _material_property_dependencies.insert(mat_dependencies.begin(), mat_dependencies.end());
115 
116  // ensure that the constraint and the flux models operate on the same mesh
117  if (gap_model.parameters().get<bool>("use_displaced_mesh") != use_displaced_mesh)
118  paramError(
119  "use_displaced_mesh",
120  "The gap flux model '",
121  name,
122  "' should operate on the same mesh (displaced/undisplaced) as the constraint object");
123 
124  // add gap model to list
125  _gap_flux_models.push_back(&gap_model);
126  }
127 }
128 
129 void
131 {
133  const auto & subdomainIDs = _mesh.meshSubdomains();
134 
135  // make sure all subdomains are using the same coordinate system
136  Moose::CoordinateSystemType coord_system = feProblem().getCoordSystem(*subdomainIDs.begin());
137  for (auto subdomain : subdomainIDs)
138  if (feProblem().getCoordSystem(subdomain) != coord_system)
139  mooseError("ModularGapConductanceConstraint requires all subdomains to have the same "
140  "coordinate system.");
141 
142  // Select proper coordinate system and geometry (plate, cylinder, sphere)
144  _pars, coord_system, feProblem().getAxisymmetricRadialCoord(), _gap_geometry_type);
145 }
146 
147 void
149  const InputParameters & params,
150  const Moose::CoordinateSystemType coord_sys,
151  unsigned int axisymmetric_radial_coord,
152  GapGeometry & gap_geometry_type)
153 {
154 
155  // Determine what type of gap geometry we are dealing with
156  // Either user input or from system's coordinate systems
157  if (gap_geometry_type == GapGeometry::AUTO)
158  {
159  if (coord_sys == Moose::COORD_XYZ)
160  gap_geometry_type = GapGeometry::PLATE;
161  else if (coord_sys == Moose::COORD_RZ)
162  gap_geometry_type = GapGeometry::CYLINDER;
163  else if (coord_sys == Moose::COORD_RSPHERICAL)
164  gap_geometry_type = GapGeometry::SPHERE;
165  else
166  mooseError("Internal Error");
167  }
168 
169  if (params.isParamValid("cylinder_axis_point_1") != params.isParamValid("cylinder_axis_point_2"))
170  paramError(
171  "cylinder_axis_point_1",
172  "Either specify both `cylinder_axis_point_1` and `cylinder_axis_point_2` or neither.");
173 
174  // Check consistency of geometry information
175  // Inform the user of needed input according to gap geometry (if not PLATE)
176  if (gap_geometry_type == GapGeometry::PLATE)
177  {
178  if (coord_sys == Moose::COORD_RSPHERICAL)
179  paramError("gap_geometry_type",
180  "'gap_geometry_type = PLATE' cannot be used with models having a spherical "
181  "coordinate system.");
182  }
183  else if (gap_geometry_type == GapGeometry::CYLINDER)
184  {
185  if (coord_sys == Moose::COORD_XYZ)
186  {
187  if (params.isParamValid("cylinder_axis_point_1") &&
188  params.isParamValid("cylinder_axis_point_2"))
189  {
190  _p1 = params.get<RealVectorValue>("cylinder_axis_point_1");
191  _p2 = params.get<RealVectorValue>("cylinder_axis_point_2");
192  }
193  else if (_mesh.dimension() == 3)
194  paramError("gap_geometry_type",
195  "For 'gap_geometry_type = CYLINDER' to be used with a Cartesian model in 3D, "
196  "'cylinder_axis_point_1' and 'cylinder_axis_point_2' must be specified.");
197  else
198  {
201  "ModularGapConductanceConstraint '", name(), "' deduced cylinder axis as ", _p1, _p2);
202  }
203  }
204  else if (coord_sys == Moose::COORD_RZ)
205  {
206  if (params.isParamValid("cylinder_axis_point_1") ||
207  params.isParamValid("cylinder_axis_point_2"))
208  paramError("cylinder_axis_point_1",
209  "The 'cylinder_axis_point_1' and 'cylinder_axis_point_2' cannot be specified "
210  "with axisymmetric models. The y-axis is used as the cylindrical axis of "
211  "symmetry.");
212 
213  if (axisymmetric_radial_coord == 0) // R-Z problem
214  {
215  _p1 = Point(0, 0, 0);
216  _p2 = Point(0, 1, 0);
217  }
218  else // Z-R problem
219  {
220  _p1 = Point(0, 0, 0);
221  _p2 = Point(1, 0, 0);
222  }
223  }
224  else if (coord_sys == Moose::COORD_RSPHERICAL)
225  paramError("gap_geometry_type",
226  "'gap_geometry_type = CYLINDER' cannot be used with models having a spherical "
227  "coordinate system.");
228  }
229  else if (gap_geometry_type == GapGeometry::SPHERE)
230  {
231  if (coord_sys == Moose::COORD_XYZ || coord_sys == Moose::COORD_RZ)
232  {
233  if (params.isParamValid("sphere_origin"))
234  _p1 = params.get<RealVectorValue>("sphere_origin");
235  else if (coord_sys == Moose::COORD_XYZ)
236  {
239  "ModularGapConductanceConstraint '", name(), "' deduced sphere origin as ", _p1);
240  }
241  else
242  paramError("gap_geometry_type",
243  "For 'gap_geometry_type = SPHERE' to be used with an axisymmetric "
244  "model, 'sphere_origin' must be specified.");
245  }
246  else if (coord_sys == Moose::COORD_RSPHERICAL)
247  {
248  if (params.isParamValid("sphere_origin"))
249  paramError("sphere_origin",
250  "The 'sphere_origin' cannot be specified with spherical models. x=0 is used "
251  "as the spherical origin.");
252  _p1 = Point(0, 0, 0);
253  }
254  }
255 }
256 
257 ADReal
259 {
260 
261  ADReal surface_integration_factor = 1.0;
262 
264  surface_integration_factor = 0.5 * (_r1 + _r2) / _radius;
266  surface_integration_factor = 0.25 * (_r1 + _r2) * (_r1 + _r2) / (_radius * _radius);
267 
268  return surface_integration_factor;
269 }
270 
271 ADReal
273 {
274  using std::log, std::min;
275 
277  {
278  const auto denominator = _radius * log(_r2 / _r1);
279  return min(denominator, _max_gap);
280  }
282  {
283  const auto denominator = _radius * _radius * ((1.0 / _r1) - (1.0 / _r2));
284  return min(denominator, _max_gap);
285  }
286  else
287  return min(_r2 - _r1, _max_gap);
288 }
289 
290 void
292 {
293  const Point & current_point = _q_point[_qp];
294 
296  {
297  // The vector _p1 + t*(_p2-_p1) defines the cylindrical axis. The point along this
298  // axis closest to current_point is found by the following for t:
299  const Point p2p1(_p2 - _p1);
300  const Point p1pc(_p1 - current_point);
301  const Real t = -(p1pc * p2p1) / p2p1.norm_sq();
302 
303  // The nearest point on the cylindrical axis to current_point is p.
304  const Point p(_p1 + t * p2p1);
305  Point rad_vec(current_point - p);
306  Real rad = rad_vec.norm();
307  rad_vec /= rad;
308  Real rad_dot_norm = rad_vec * _normals[_qp];
309 
310  if (rad_dot_norm > 0)
311  {
312  _r1 = rad;
313  _r2 = rad + gap_length;
314  _radius = _r1;
315  }
316  else if (rad_dot_norm < 0)
317  {
318  _r1 = rad - gap_length;
319  _r2 = rad;
320  _radius = _r2;
321  }
322  else
323  mooseError("Issue with cylindrical flux calculation normals.\n");
324  }
326  {
327  const Point origin_to_curr_point(current_point - _p1);
328  const Real normal_dot = origin_to_curr_point * _normals[_qp];
329  const Real curr_point_radius = origin_to_curr_point.norm();
330  if (normal_dot > 0) // on inside surface
331  {
332  _r1 = curr_point_radius;
333  _r2 = curr_point_radius + gap_length;
334  _radius = _r1;
335  }
336  else if (normal_dot < 0) // on outside surface
337  {
338  _r1 = curr_point_radius - gap_length;
339  _r2 = curr_point_radius;
340  _radius = _r2;
341  }
342  else
343  mooseError("Issue with spherical flux calculation normals. \n");
344  }
345  else
346  {
347  _r2 = gap_length;
348  _r1 = 0;
349  _radius = 0;
350  }
351 }
352 
353 ADReal
355 {
356  switch (mortar_type)
357  {
359  return _lambda[_qp] * _test_primary[_i][_qp];
360 
362  return -_lambda[_qp] * _test_secondary[_i][_qp];
363 
365  {
366  // we are creating an AD version of phys points primary and secondary here...
367  ADRealVectorValue ad_phys_points_primary = _phys_points_primary[_qp];
368  ADRealVectorValue ad_phys_points_secondary = _phys_points_secondary[_qp];
369 
370  // ...which uses the derivative vector of the primary and secondary displacements as
371  // an approximation of the true phys points derivatives when the mesh is displacing
372  if (_displaced)
373  {
374  // Trim interior node variable derivatives
375  const auto & primary_ip_lowerd_map = amg().getPrimaryIpToLowerElementMap(
377  const auto & secondary_ip_lowerd_map =
379 
380  std::array<const MooseVariable *, 3> var_array{{_disp_x_var, _disp_y_var, _disp_z_var}};
381  std::array<ADReal, 3> primary_disp;
382  std::array<ADReal, 3> secondary_disp;
383 
384  for (unsigned int i = 0; i < _n_disp; ++i)
385  {
386  primary_disp[i] = (*_disp_primary[i])[_qp];
387  secondary_disp[i] = (*_disp_secondary[i])[_qp];
388  }
389 
390  trimInteriorNodeDerivatives(primary_ip_lowerd_map, var_array, primary_disp, false);
391  trimInteriorNodeDerivatives(secondary_ip_lowerd_map, var_array, secondary_disp, true);
392 
393  // Populate quantities with trimmed derivatives
394  for (unsigned int i = 0; i < _n_disp; ++i)
395  {
396  ad_phys_points_primary(i).derivatives() = primary_disp[i].derivatives();
397  ad_phys_points_secondary(i).derivatives() = secondary_disp[i].derivatives();
398  }
399  }
400 
401  // compute an ADReal gap width to pass to each gap flux model
402  _gap_width = (ad_phys_points_primary - ad_phys_points_secondary) * _normals[_qp];
403 
404  // First, compute radii with _gap_width
406 
407  // With radii, compute adjusted gap length for conduction
409 
410  // Ensure energy balance for non-flat (non-PLATE) general geometries when using radiation
412 
413  // Sum up all flux contributions from all supplied gap flux models
414  ADReal flux = 0.0;
415  for (const auto & model : _gap_flux_models)
416  flux += model->computeFluxInternal(*this);
417 
418  // The Lagrange multiplier _is_ the gap flux
419  return (_lambda[_qp] - flux) * _test[_i][_qp];
420  }
421 
422  default:
423  return 0;
424  }
425 }
426 
427 void
429 {
430  Point position;
431  Real area = 0.0;
432  const auto my_pid = processor_id();
433 
434  // build side element list as (element, side, id) tuples
435  const auto bnd = _mesh.buildActiveSideList();
436 
437  std::unique_ptr<const Elem> side_ptr;
438  for (auto [elem_id, side, id] : bnd)
439  if (id == _primary_id)
440  {
441  const auto * elem = _mesh.elemPtr(elem_id);
442  if (elem->processor_id() != my_pid)
443  continue;
444 
445  // update side_ptr
446  elem->side_ptr(side_ptr, side);
447 
448  // area of the (linearized) side
449  const auto side_area = side_ptr->volume();
450 
451  // position of the side
452  const auto side_position = side_ptr->true_centroid();
453 
454  // sum up
455  position += side_position * side_area;
456  area += side_area;
457  }
458 
459  // parallel communication
460  _communicator.sum(position);
461  _communicator.sum(area);
462 
463  // set axis
464  if (area == 0.0)
465  {
467  paramError("gap_geometry_type",
468  "Unable to decuce cylinder axis automatically, please specify "
469  "'cylinder_axis_point_1' and 'cylinder_axis_point_2'.");
471  paramError("gap_geometry_type",
472  "Unable to decuce sphere origin automatically, please specify "
473  "'sphere_origin'.");
474  else
475  mooseError("Internal error.");
476  }
477 
478  _p1 = position / area;
479  _p2 = _p1 + Point(0, 0, 1);
480 }
MooseMesh & _mesh
const std::vector< Point > & _q_point
std::vector< const ADVariableValue * > _disp_primary
const VariableTestValue & _test_secondary
const InputParameters & _pars
const MooseVariable *const _disp_y_var
y-displacement variable
void paramError(const std::string &param, Args... args) const
virtual Elem * elemPtr(const dof_id_type i)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
std::vector< std::tuple< dof_id_type, unsigned short int, boundary_id_type > > buildActiveSideList() const
std::map< unsigned int, unsigned int > getPrimaryIpToLowerElementMap(const Elem &primary_elem, const Elem &primary_elem_ip, const Elem &lower_secondary_elem) const
std::vector< std::pair< R1, R2 > > get(const std::string &param1, const std::string &param2) const
void deduceGeometryParameters()
Automatically set up axis/center for 2D cartesian problems with cylindrical/spherical gap geometry...
ADReal _r1
Radii for quadrature points.
void mooseInfoRepeated(Args &&... args)
virtual ADReal computeQpResidual(Moose::MortarType mortar_type) override
Computes the residual for the LM equation, lambda = (k/l)*(T^(1) - PT^(2)).
const MooseArray< Point > & _phys_points_primary
Elem const *const & _lower_primary_elem
COORD_RSPHERICAL
const Parallel::Communicator & _communicator
unsigned int _i
DualNumber< Real, DNDerivativeType, true > ADReal
const ADVariableValue & _lambda
registerMooseObject("HeatTransferApp", ModularGapConductanceConstraint)
const VariableTestValue & _test
Elem const *const & _lower_secondary_elem
const std::string & name() const
ModularGapConductanceConstraint(const InputParameters &parameters)
const AutomaticMortarGeneration & amg() const
static void trimInteriorNodeDerivatives(const std::map< unsigned int, unsigned int > &primary_ip_lowerd_map, const Variables &moose_var, DualNumbers &ad_vars, const bool is_secondary)
virtual unsigned int dimension() const
std::vector< const ADVariableValue * > _disp_secondary
std::vector< Point > _normals
SubProblem & _subproblem
GapGeometry
Gap geometry (user input or internally overwritten)
const std::vector< std::string > _disp_name
Displacement variables.
void computeGapRadii(const ADReal &gap_length)
Computes radii as a function of point and geometry.
std::vector< const GapFluxModelBase * > _gap_flux_models
Gap flux models.
virtual MooseVariable & getStandardVariable(const THREAD_ID tid, const std::string &var_name)=0
std::map< unsigned int, unsigned int > getSecondaryIpToLowerElementMap(const Elem &lower_secondary_elem) const
const MooseVariable *const _disp_x_var
x-displacement variable
const MooseArray< Point > & _phys_points_secondary
const FEProblemBase & feProblem() const
auto log(const T &)
const PertinentGeochemicalSystem model(database, {"H2O", "H+", "HCO3-", "O2(aq)", "Ca++", ">(s)FeOH", "radius_neg1", "radius_neg1.5"}, {"Calcite"}, {}, {"Calcite_asdf"}, {"CH4(aq)"}, {">(s)FeOCa+"}, "O2(aq)", "e-")
void addCoupledVar(const std::string &name, const std::string &doc_string)
const bool _displaced
const BoundaryID _primary_id
void addMooseVariableDependency(MooseVariableFieldBase *var)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
std::vector< UserObjectName > _gap_flux_model_names
Gap flux model names.
CoordinateSystemType
virtual void setGapGeometryParameters(const InputParameters &params, const Moose::CoordinateSystemType coord_sys, unsigned int axisymmetric_radial_coord, GapGeometry &gap_geometry_type)
void mooseError(Args &&... args) const
Point & _p1
Points for geometric definitions.
enum ModularGapConductanceConstraint::GapGeometry _gap_geometry_type
This Constraint implements thermal contact using a "gap conductance" model in which the flux is repre...
void addClassDescription(const std::string &doc_string)
static InputParameters validParams()
Moose::CoordinateSystemType getCoordSystem(SubdomainID sid) const
const MooseVariable *const _disp_z_var
z-displacement variable
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
void paramWarning(const std::string &param, Args... args) const
ADReal _surface_integration_factor
Factor to preserve energy balance (due to mismatch in primary/secondary differential surface sizes) ...
processor_id_type processor_id() const
auto min(const L &left, const R &right)
const VariableTestValue & _test_primary
unsigned int _qp
std::unordered_set< unsigned int > _material_property_dependencies
const std::set< SubdomainID > & meshSubdomains() const
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
bool isParamValid(const std::string &name) const
ADReal _gap_width
Gap width to pass into flux models.