https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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{
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");
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)
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
129void
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
147void
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"))
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
257ADReal
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
271ADReal
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
290void
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
353ADReal
355{
356 switch (mortar_type)
357 {
358 case Moose::MortarType::Primary:
359 return _lambda[_qp] * _test_primary[_i][_qp];
360
361 case Moose::MortarType::Secondary:
362 return -_lambda[_qp] * _test_secondary[_i][_qp];
363
364 case Moose::MortarType::Lower:
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
427void
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}
DualNumber< Real, DNDerivativeType, true > ADReal
const Real p
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-")
registerMooseObject("HeatTransferApp", ModularGapConductanceConstraint)
void mooseInfoRepeated(Args &&... args)
const ADVariableValue & _lambda
static InputParameters validParams()
std::map< unsigned int, unsigned int > getSecondaryIpToLowerElementMap(const Elem &lower_secondary_elem) const
std::map< unsigned int, unsigned int > getPrimaryIpToLowerElementMap(const Elem &primary_elem, const Elem &primary_elem_ip, const Elem &lower_secondary_elem) const
unsigned int _qp
unsigned int _i
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
std::vector< std::pair< R1, R2 > > get(const std::string &param1, const std::string &param2) const
void addClassDescription(const std::string &doc_string)
void addCoupledVar(const std::string &name, const std::string &doc_string)
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
bool isParamValid(const std::string &name) const
std::unordered_set< unsigned int > _material_property_dependencies
This Constraint implements thermal contact using a "gap conductance" model in which the flux is repre...
const MooseVariable *const _disp_x_var
x-displacement variable
void computeGapRadii(const ADReal &gap_length)
Computes radii as a function of point and geometry.
const MooseVariable *const _disp_z_var
z-displacement variable
std::vector< const GapFluxModelBase * > _gap_flux_models
Gap flux models.
virtual ADReal computeQpResidual(Moose::MortarType mortar_type) override
Computes the residual for the LM equation, lambda = (k/l)*(T^(1) - PT^(2)).
ModularGapConductanceConstraint(const InputParameters &parameters)
ADReal _gap_width
Gap width to pass into flux models.
ADReal _surface_integration_factor
Factor to preserve energy balance (due to mismatch in primary/secondary differential surface sizes)
std::vector< const ADVariableValue * > _disp_secondary
Point & _p1
Points for geometric definitions.
std::vector< UserObjectName > _gap_flux_model_names
Gap flux model names.
std::vector< const ADVariableValue * > _disp_primary
ADReal _r1
Radii for quadrature points.
const MooseVariable *const _disp_y_var
y-displacement variable
virtual void setGapGeometryParameters(const InputParameters &params, const Moose::CoordinateSystemType coord_sys, unsigned int axisymmetric_radial_coord, GapGeometry &gap_geometry_type)
const std::vector< std::string > _disp_name
Displacement variables.
GapGeometry
Gap geometry (user input or internally overwritten)
enum ModularGapConductanceConstraint::GapGeometry _gap_geometry_type
void deduceGeometryParameters()
Automatically set up axis/center for 2D cartesian problems with cylindrical/spherical gap geometry.
void paramWarning(const std::string &param, Args... args) const
const std::string & name() const
void paramError(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
const InputParameters & _pars
virtual unsigned int dimension() const
virtual Elem * elemPtr(const dof_id_type i)
const std::set< SubdomainID > & meshSubdomains() const
std::vector< std::tuple< dof_id_type, unsigned short int, boundary_id_type > > buildActiveSideList() const
void addMooseVariableDependency(MooseVariableFieldBase *var)
const ADTemplateVariableValue< OutputType > & adSln() const override
const VariableTestValue & _test_secondary
const FEProblemBase & feProblem() const
const VariableTestValue & _test
const std::vector< Point > & _q_point
const VariableTestValue & _test_primary
const bool _displaced
Elem const *const & _lower_secondary_elem
const AutomaticMortarGeneration & amg() const
const BoundaryID _primary_id
Elem const *const & _lower_primary_elem
const MooseArray< Point > & _phys_points_secondary
const MooseArray< Point > & _phys_points_primary
std::vector< Point > _normals
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)
MooseMesh & _mesh
SubProblem & _subproblem
virtual MooseVariable & getStandardVariable(const THREAD_ID tid, const std::string &var_name)=0
Moose::CoordinateSystemType getCoordSystem(SubdomainID sid) const
const Parallel::Communicator & _communicator
processor_id_type processor_id() const
CoordinateSystemType
COORD_RSPHERICAL