www.mooseframework.org
SpiralAnnularMesh.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://www.mooseframework.org
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 #include "SpiralAnnularMesh.h"
11 
12 #include "libmesh/face_quad4.h"
13 #include "libmesh/face_tri3.h"
14 
16 
18 
21 {
23  params.addRequiredRangeCheckedParam<Real>(
24  "inner_radius", "inner_radius>0.", "The size of the inner circle.");
25  params.addRequiredRangeCheckedParam<Real>("outer_radius",
26  "outer_radius>0.",
27  "The size of the outer circle."
28  " Logically, it has to be greater than inner_radius");
29  params.addRequiredRangeCheckedParam<unsigned int>(
30  "nodes_per_ring", "nodes_per_ring>5", "Number of nodes on each ring.");
31  params.addParam<bool>(
32  "use_tri6", false, "Generate mesh of TRI6 elements instead of TRI3 elements.");
33  params.addRequiredRangeCheckedParam<unsigned int>(
34  "num_rings", "num_rings>1", "The number of rings.");
35  params.addParam<boundary_id_type>(
36  "cylinder_bid", 1, "The boundary id to use for the cylinder (inner circle)");
37  params.addParam<boundary_id_type>(
38  "exterior_bid", 2, "The boundary id to use for the exterior (outer circle)");
39  params.addParam<Real>("initial_delta_r",
40  "Width of the initial layer of elements around the cylinder."
41  "This number should be approximately"
42  " 2 * pi * inner_radius / nodes_per_ring to ensure that the"
43  " initial layer of elements is almost equilateral");
44  params.addClassDescription("Creates an annual mesh based on TRI3 elements"
45  " (it can also be TRI6 elements) on several rings.");
46 
47  return params;
48 }
49 
51  : MooseMesh(parameters),
52  _inner_radius(getParam<Real>("inner_radius")),
53  _outer_radius(getParam<Real>("outer_radius")),
54  _radial_bias(1.0),
55  _nodes_per_ring(getParam<unsigned int>("nodes_per_ring")),
56  _use_tri6(getParam<bool>("use_tri6")),
57  _num_rings(getParam<unsigned int>("num_rings")),
58  _cylinder_bid(getParam<boundary_id_type>("cylinder_bid")),
59  _exterior_bid(getParam<boundary_id_type>("exterior_bid")),
60  _initial_delta_r(2 * libMesh::pi * _inner_radius / _nodes_per_ring)
61 {
62  // catch likely user errors
64  mooseError("SpiralAnnularMesh: outer_radius must be greater than inner_radius");
65 }
66 
67 std::unique_ptr<MooseMesh>
69 {
70  return libmesh_make_unique<SpiralAnnularMesh>(*this);
71 }
72 
73 void
75 {
76  {
77  // Compute the radial bias given:
78  // .) the inner radius
79  // .) the outer radius
80  // .) the initial_delta_r
81  // .) the desired number of intervals
82  // Note: the exponent n used in the formula is one less than the
83  // number of rings the user requests.
84  Real alpha = 1.1;
85  int n = _num_rings - 1;
86 
87  // lambda used to compute the residual and Jacobian for the Newton iterations.
88  // We capture parameters which don't need to change from the current scope at
89  // the time this lambda is declared. The values are not updated later, so we
90  // can't use this for e.g. f, df, and alpha.
91  auto newton = [this, n](Real & f, Real & df, const Real & alpha) {
92  f = (1. - std::pow(alpha, n + 1)) / (1. - alpha) -
94  df = (-(n + 1) * (1 - alpha) * std::pow(alpha, n) + (1. - std::pow(alpha, n + 1))) /
95  (1. - alpha) / (1. - alpha);
96  };
97 
98  Real f, df;
99  int num_iter = 1;
100  newton(f, df, alpha);
101 
102  while (std::abs(f) > 1.e-9 && num_iter <= 25)
103  {
104  // Compute and apply update.
105  Real dx = -f / df;
106  alpha += dx;
107  newton(f, df, alpha);
108  num_iter++;
109  }
110 
111  // In case the Newton iteration fails to converge.
112  if (num_iter > 25)
113  mooseError("Newton iteration failed to converge (more than 25 iterations).");
114 
115  // Set radial basis to the value of alpha that we computed with Newton.
116  _radial_bias = alpha;
117  }
118 
119  // The number of rings specified by the user does not include the ring at
120  // the surface of the cylinder itself, so we increment it by one now.
121  _num_rings += 1;
122 
123  // Mesh we are eventually going to create.
124  MeshBase & mesh = getMesh();
125 
126  // Data structure that holds pointers to the Nodes of each ring.
127  std::vector<std::vector<Node *>> ring_nodes(_num_rings);
128 
129  // Initialize radius and delta_r variables.
130  Real radius = _inner_radius;
131  Real delta_r = _initial_delta_r;
132 
133  // Node id counter.
134  unsigned int current_node_id = 0;
135 
136  for (std::size_t r = 0; r < _num_rings; ++r)
137  {
138  ring_nodes[r].resize(_nodes_per_ring);
139 
140  // Add nodes starting from either theta=0 or theta=pi/nodes_per_ring
141  Real theta = r % 2 == 0 ? 0 : (libMesh::pi / _nodes_per_ring);
142  for (std::size_t n = 0; n < _nodes_per_ring; ++n)
143  {
144  ring_nodes[r][n] = mesh.add_point(Point(radius * std::cos(theta), radius * std::sin(theta)),
145  current_node_id++);
146  // Update angle
147  theta += 2 * libMesh::pi / _nodes_per_ring;
148  }
149 
150  // Go to next ring
151  radius += delta_r;
152  delta_r *= _radial_bias;
153  }
154 
155  // Add elements
156  for (std::size_t r = 0; r < _num_rings - 1; ++r)
157  {
158  // even -> odd ring
159  if (r % 2 == 0)
160  {
161  // Inner ring (n, n*, n+1)
162  // Starred indices refer to nodes on the "outer" ring of this pair.
163  for (std::size_t n = 0; n < _nodes_per_ring; ++n)
164  {
165  // Wrap around
166  unsigned int np1 = (n == _nodes_per_ring - 1) ? 0 : n + 1;
167  Elem * elem = mesh.add_elem(new Tri3);
168  elem->set_node(0) = ring_nodes[r][n];
169  elem->set_node(1) = ring_nodes[r + 1][n];
170  elem->set_node(2) = ring_nodes[r][np1];
171 
172  // Add interior faces to 'cylinder' sideset if we are on ring 0.
173  if (r == 0)
174  mesh.boundary_info->add_side(elem->id(), /*side=*/2, _cylinder_bid);
175  }
176 
177  // Outer ring (n*, n+1*, n+1)
178  for (std::size_t n = 0; n < _nodes_per_ring; ++n)
179  {
180  // Wrap around
181  unsigned int np1 = (n == _nodes_per_ring - 1) ? 0 : n + 1;
182  Elem * elem = mesh.add_elem(new Tri3);
183  elem->set_node(0) = ring_nodes[r + 1][n];
184  elem->set_node(1) = ring_nodes[r + 1][np1];
185  elem->set_node(2) = ring_nodes[r][np1];
186 
187  // Add exterior faces to 'exterior' sideset if we're on the last ring.
188  // Note: this code appears in two places since we could end on either an even or odd ring.
189  if (r == _num_rings - 2)
190  mesh.boundary_info->add_side(elem->id(), /*side=*/0, _exterior_bid);
191  }
192  }
193  else
194  {
195  // odd -> even ring
196  // Inner ring (n, n+1*, n+1)
197  for (std::size_t n = 0; n < _nodes_per_ring; ++n)
198  {
199  // Wrap around
200  unsigned int np1 = (n == _nodes_per_ring - 1) ? 0 : n + 1;
201  Elem * elem = mesh.add_elem(new Tri3);
202  elem->set_node(0) = ring_nodes[r][n];
203  elem->set_node(1) = ring_nodes[r + 1][np1];
204  elem->set_node(2) = ring_nodes[r][np1];
205  }
206 
207  // Outer ring (n*, n+1*, n)
208  for (std::size_t n = 0; n < _nodes_per_ring; ++n)
209  {
210  // Wrap around
211  unsigned int np1 = (n == _nodes_per_ring - 1) ? 0 : n + 1;
212  Elem * elem = mesh.add_elem(new Tri3);
213  elem->set_node(0) = ring_nodes[r + 1][n];
214  elem->set_node(1) = ring_nodes[r + 1][np1];
215  elem->set_node(2) = ring_nodes[r][n];
216 
217  // Add exterior faces to 'exterior' sideset if we're on the last ring.
218  if (r == _num_rings - 2)
219  mesh.boundary_info->add_side(elem->id(), /*side=*/0, _exterior_bid);
220  }
221  }
222  }
223 
224  // Sanity check: make sure all elements have positive area. Note: we
225  // can't use elem->volume() for this, as that always returns a
226  // positive area regardless of the node ordering.
227  // We compute (p1-p0) \cross (p2-p0) and check that the z-component is positive.
228  for (const auto & elem : mesh.element_ptr_range())
229  {
230  Point cp = (elem->point(1) - elem->point(0)).cross(elem->point(2) - elem->point(0));
231  if (cp(2) < 0.)
232  mooseError("Invalid elem found with negative area");
233  }
234 
235  // Create sideset names.
236  mesh.boundary_info->sideset_name(_cylinder_bid) = "cylinder";
237  mesh.boundary_info->sideset_name(_exterior_bid) = "exterior";
238 
239  // Find neighbors, etc.
240  mesh.prepare_for_use();
241 
242  if (_use_tri6)
243  {
244  mesh.all_second_order(/*full_ordered=*/true);
245  std::vector<unsigned int> nos;
246 
247  // Loop over the elements, moving mid-edge nodes onto the
248  // nearest radius as applicable. For each element, exactly one
249  // edge should lie on the same radius, so we move only that
250  // mid-edge node.
251  for (const auto & elem : mesh.element_ptr_range())
252  {
253  // Make sure we are dealing only with triangles
254  libmesh_assert(elem->n_vertices() == 3);
255 
256  // Compute vertex radii
257  Real radii[3] = {elem->point(0).norm(), elem->point(1).norm(), elem->point(2).norm()};
258 
259  // Compute absolute differences between radii so we can determine which two are on the same
260  // circular arc.
261  Real dr[3] = {std::abs(radii[0] - radii[1]),
262  std::abs(radii[1] - radii[2]),
263  std::abs(radii[2] - radii[0])};
264 
265  // Compute index of minimum dr.
266  auto index = std::distance(std::begin(dr), std::min_element(std::begin(dr), std::end(dr)));
267 
268  // Make sure that the minimum found is also (almost) zero.
269  if (dr[index] > TOLERANCE)
270  mooseError("Error: element had no sides with nodes on same radius.");
271 
272  // Get list of all local node ids on this side. The first
273  // two entries in nos correspond to the vertices, the last
274  // entry corresponds to the mid-edge node.
275  nos = elem->nodes_on_side(index);
276 
277  // Compute the angles associated with nodes nos[0] and nos[1].
278  Real theta0 = std::atan2(elem->point(nos[0])(1), elem->point(nos[0])(0)),
279  theta1 = std::atan2(elem->point(nos[1])(1), elem->point(nos[1])(0));
280 
281  // atan2 returns values in the range (-pi, pi). If theta0
282  // and theta1 have the same sign, we can simply average them
283  // to get half of the acute angle between them. On the other
284  // hand, if theta0 and theta1 are of opposite sign _and_ both
285  // are larger than pi/2, we need to add 2*pi when averaging,
286  // otherwise we will get half of the _obtuse_ angle between
287  // them, and the point will flip to the other side of the
288  // circle (see below).
289  Real new_theta = 0.5 * (theta0 + theta1);
290 
291  // It should not be possible for both:
292  // 1.) |theta0| > pi/2, and
293  // 2.) |theta1| < pi/2
294  // as this would not be a well-formed element.
295  if ((theta0 * theta1 < 0) && (std::abs(theta0) > 0.5 * libMesh::pi) &&
296  (std::abs(theta1) > 0.5 * libMesh::pi))
297  new_theta = 0.5 * (theta0 + theta1 + 2 * libMesh::pi);
298 
299  // The new radius will be the radius of point nos[0] or nos[1] (they are the same!).
300  Real new_r = elem->point(nos[0]).norm();
301 
302  // Finally, move the point to its new location.
303  elem->point(nos[2]) = Point(new_r * std::cos(new_theta), new_r * std::sin(new_theta), 0.);
304  }
305  }
306 }
SpiralAnnularMesh.h
SpiralAnnularMesh::safeClone
virtual std::unique_ptr< MooseMesh > safeClone() const override
A safer version of the clone() method that hands back an allocated object wrapped in a smart pointer.
Definition: SpiralAnnularMesh.C:68
SpiralAnnularMesh::_nodes_per_ring
const unsigned int _nodes_per_ring
Number of nodes on each ring.
Definition: SpiralAnnularMesh.h:49
MooseObject::mooseError
void mooseError(Args &&... args) const
Definition: MooseObject.h:141
SpiralAnnularMesh::_outer_radius
const Real _outer_radius
Radius of the outer circle. Logically, it's bigger that inner_radius.
Definition: SpiralAnnularMesh.h:42
std::abs
MetaPhysicL::DualNumber< T, D > abs(const MetaPhysicL::DualNumber< T, D > &in)
libMesh
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
Definition: AddPeriodicBCAction.h:16
InputParameters::addParam
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an option parameter and a documentation string to the InputParameters object.
Definition: InputParameters.h:1198
defineLegacyParams
defineLegacyParams(SpiralAnnularMesh)
SpiralAnnularMesh::_inner_radius
const Real _inner_radius
Radius of the inner circle.
Definition: SpiralAnnularMesh.h:39
SpiralAnnularMesh::_radial_bias
Real _radial_bias
Factor to increase initial_delta_r for each ring.
Definition: SpiralAnnularMesh.h:46
SpiralAnnularMesh::_exterior_bid
const boundary_id_type _exterior_bid
Definition: SpiralAnnularMesh.h:61
InputParameters
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
Definition: InputParameters.h:53
SpiralAnnularMesh
Mesh generated from parameters.
Definition: SpiralAnnularMesh.h:22
SpiralAnnularMesh::_num_rings
unsigned int _num_rings
Number of rings.You can't specify both the number of rings and the radial bias if you want to match a...
Definition: SpiralAnnularMesh.h:58
MooseMesh::getMesh
MeshBase & getMesh()
Accessor for the underlying libMesh Mesh object.
Definition: MooseMesh.C:2599
SpiralAnnularMesh::SpiralAnnularMesh
SpiralAnnularMesh(const InputParameters &parameters)
Definition: SpiralAnnularMesh.C:50
MooseMesh::elem
virtual Elem * elem(const dof_id_type i)
Various accessors (pointers/references) for Elem "i".
Definition: MooseMesh.C:2275
InputParameters::addClassDescription
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump.
Definition: InputParameters.C:70
SpiralAnnularMesh::_initial_delta_r
const Real _initial_delta_r
Definition: SpiralAnnularMesh.h:67
InputParameters::addRequiredRangeCheckedParam
void addRequiredRangeCheckedParam(const std::string &name, const std::string &parsed_function, const std::string &doc_string)
These methods add an range checked parameters.
Definition: InputParameters.h:1235
MathUtils::pow
T pow(T x, int e)
Definition: MathUtils.h:44
registerMooseObject
registerMooseObject("MooseApp", SpiralAnnularMesh)
SpiralAnnularMesh::_cylinder_bid
const boundary_id_type _cylinder_bid
The boundary id to use for the cylinder.
Definition: SpiralAnnularMesh.h:61
SpiralAnnularMesh::validParams
static InputParameters validParams()
Definition: SpiralAnnularMesh.C:20
SpiralAnnularMesh::buildMesh
virtual void buildMesh() override
Must be overridden by child classes.
Definition: SpiralAnnularMesh.C:74
MooseMesh
MooseMesh wraps a libMesh::Mesh object and enhances its capabilities by caching additional data and s...
Definition: MooseMesh.h:74
SpiralAnnularMesh::_use_tri6
const bool _use_tri6
Generate mesh of TRI6 elements instead of TRI3 elements.
Definition: SpiralAnnularMesh.h:52
MooseMesh::validParams
static InputParameters validParams()
Typical "Moose-style" constructor and copy constructor.
Definition: MooseMesh.C:65
n
PetscInt n
Definition: PetscDMMoose.C:1504