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