https://mooseframework.inl.gov
Loading...
Searching...
No Matches
MoveNodesByParsedExpressionModifier.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 "Function.h"
12#include "MooseVariableFE.h"
13#include "SystemBase.h"
14#include "AuxiliarySystem.h"
15#include "FEProblemBase.h"
16#include "MooseMesh.h"
17#include "Assembly.h"
18
19#include "libmesh/elem.h"
20#include "libmesh/mesh_base.h"
21#include "libmesh/parallel_ghost_sync.h"
22#include <libmesh/int_range.h>
23#include <algorithm>
24#include <set>
25
27
29 "displacement_x", "displacement_y", "displacement_z"};
30
33{
38 "Actively displaces the selected mesh nodes by parsed expressions for the x, y, and z "
39 "displacement components, evaluated relative to each node's original position.");
40
41 params.addParam<std::vector<BoundaryName>>(
42 "boundary", {}, "List of boundaries whose nodes are displaced");
43 params.addParam<std::vector<SubdomainName>>(
44 "block",
45 {},
46 "List of blocks whose nodes are displaced. If neither 'block' nor 'boundary' is specified, "
47 "all blocks in the mesh are used.");
48
49 params.addParam<ParsedFunctionExpression>(
50 _disp_name[0], "0", "Parsed expression for the displacement in the x direction");
51 params.addParam<ParsedFunctionExpression>(
52 _disp_name[1], "0", "Parsed expression for the displacement in the y direction");
53 params.addParam<ParsedFunctionExpression>(
54 _disp_name[2], "0", "Parsed expression for the displacement in the z direction");
55
56 params.addParam<std::vector<VariableName>>(
57 "coupled_variables", {}, "Nodal variables usable as symbols in the displacement expressions");
58 params.addParam<std::vector<FunctionName>>(
59 "functions", {}, "Functions usable as symbols in the displacement expressions");
60 params.addParam<std::vector<PostprocessorName>>(
61 "postprocessors", {}, "Postprocessors usable as symbols in the displacement expressions");
62 params.addParam<std::vector<MooseFunctorName>>(
63 "functor_names",
64 {},
65 "Functors (e.g. functor material properties) usable as symbols in the displacement "
66 "expressions. They are evaluated at each node in its original (undisplaced) position.");
67 params.addParam<std::vector<std::string>>(
68 "functor_symbols",
69 {},
70 "Symbolic name to use for each functor in 'functor_names' in the displacement expressions. "
71 "If not provided, then the actual functor names will be used.");
72
73 params.addParam<std::vector<std::string>>(
74 "constant_names", {}, "Vector of constants used in the parsed function");
75 params.addParam<std::vector<std::string>>(
76 "constant_expressions",
77 {},
78 "Vector of values for the constants in constant_names (can be an FParser expression)");
79
80 // Optional outputs. Each is enabled only when its parameter is provided. The
81 // target aux variables must be created by the user in the input.
82 params.addParam<std::vector<AuxVariableName>>(
83 "original_coordinate_variables",
84 {},
85 "If set, the original (undisplaced) node coordinates are written to these three nodal "
86 "auxiliary variables, given in x, y, z order, which must be created by the user.");
87 params.addParam<std::vector<AuxVariableName>>(
88 "parsed_displacement_variables",
89 {},
90 "If set, the current node displacement (current minus original position) is written to these "
91 "three nodal auxiliary variables, given in x, y, z order, which must be created by the "
92 "user.");
93 params.addParam<AuxVariableName>(
94 "density_factor_variable",
95 "",
96 "If set, the per-element density adjustment factor (original volume divided by current "
97 "volume) is written to this elemental aux variable, which must be created by the user.");
98
99 params.addParam<bool>(
100 "notify_mesh_changed",
101 false,
102 "Whether to notify the problem that the mesh has changed (by calling meshChanged) after the "
103 "nodes are moved, so that mesh-dependent caches, the displaced mesh, geometric searches, and "
104 "outputs are updated.");
105
106 // By default don't execute; the user selects a schedule via execute_on.
107 params.set<ExecFlagEnum>("execute_on") = "NONE";
108
109 return params;
110}
111
113 const InputParameters & parameters)
114 : GeneralUserObject(parameters),
115 FunctionParserUtils<false>(parameters),
117 _mesh(_subproblem.mesh()),
118 _boundary_ids(_mesh.getBoundaryIDs(getParam<std::vector<BoundaryName>>("boundary"))),
119 _subdomain_ids(_mesh.getSubdomainIDs(getParam<std::vector<SubdomainName>>("block"))),
120 _original_position(
121 declareRestartableData<std::unordered_map<dof_id_type, Point>>("original_position")),
122 _output_coordinates(
123 !getParam<std::vector<AuxVariableName>>("original_coordinate_variables").empty()),
124 _output_displacements(
125 !getParam<std::vector<AuxVariableName>>("parsed_displacement_variables").empty()),
126 _output_density_factor(!getParam<AuxVariableName>("density_factor_variable").empty()),
127 _aux_sys_num(0),
128 _density_factor_var(0),
129 _assembly(nullptr),
130 _original_volume(
131 declareRestartableData<std::unordered_map<dof_id_type, Real>>("original_volume")),
132 _original_volume_recorded(declareRestartableData<bool>("original_volume_recorded", false)),
133 _notify_mesh_changed(getParam<bool>("notify_mesh_changed"))
134{
135 const auto & var_names = getParam<std::vector<VariableName>>("coupled_variables");
136 const auto & func_names = getParam<std::vector<FunctionName>>("functions");
137 const auto & pp_names = getParam<std::vector<PostprocessorName>>("postprocessors");
138 const auto & functor_names = getParam<std::vector<MooseFunctorName>>("functor_names");
139 const auto & functor_symbols = getParam<std::vector<std::string>>("functor_symbols");
140
141 if (!functor_symbols.empty() && functor_symbols.size() != functor_names.size())
142 paramError("functor_symbols", "functor_symbols must be the same length as functor_names.");
143
144 // The symbol each functor is referred to by: the user-provided one if given, else its name.
145 std::vector<std::string> functor_syms;
146 for (const auto i : index_range(functor_names))
147 functor_syms.push_back(functor_symbols.empty() ? std::string(functor_names[i])
148 : functor_symbols[i]);
149
150 // Build the comma-separated symbol list in evaluation order:
151 // coupled variables, functions, postprocessors, functors, then x, y, z, t.
152 std::string symbols;
153 auto add_symbol = [&symbols](const std::string & s)
154 { symbols += (symbols.empty() ? "" : ",") + s; };
155
156 for (const auto & name : var_names)
157 {
159 if (!var.isNodal())
160 paramError("coupled_variables",
161 "Variable '",
162 name,
163 "' is not nodal. Only nodal variables can be used in the displacement "
164 "expressions.");
165 _coupled_vars.push_back(&var);
166 add_symbol(name);
167 }
168 for (const auto & name : func_names)
169 {
170 _functions.push_back(&getFunctionByName(name));
171 add_symbol(name);
172 }
173 for (const auto & name : pp_names)
174 {
176 add_symbol(name);
177 }
178 for (const auto i : index_range(functor_names))
179 {
180 // A variable functor reads the variable's own partitioned solution vector, which cannot be
181 // indexed at a node owned by another processor, and functors are evaluated on every rank
182 // whenever moveNodes() is not already restricted to owned nodes. 'coupled_variables' reads
183 // nodal values safely in parallel, so variables must go through that parameter instead.
184 if (_subproblem.hasVariable(functor_names[i]))
185 paramError("functor_names",
186 "'",
187 functor_names[i],
188 "' is a variable. Use 'coupled_variables' instead, which reads nodal values "
189 "safely in parallel.");
190 _functors.push_back(&getFunctor<Real>(functor_names[i]));
191 add_symbol(functor_syms[i]);
192 }
193
194 // x, y, z, t are always available; guard against name collisions.
195 for (const auto & reserved : {"x", "y", "z", "t"})
196 {
197 if (std::find(var_names.begin(), var_names.end(), reserved) != var_names.end() ||
198 std::find(func_names.begin(), func_names.end(), reserved) != func_names.end() ||
199 std::find(pp_names.begin(), pp_names.end(), reserved) != pp_names.end() ||
200 std::find(functor_syms.begin(), functor_syms.end(), reserved) != functor_syms.end())
201 mooseError("The symbol '",
202 reserved,
203 "' is reserved for coordinates/time and cannot be used as a coupled variable, "
204 "function, postprocessor, or functor name.");
205 add_symbol(reserved);
206 }
207
208 const auto & constant_names = getParam<std::vector<std::string>>("constant_names");
209 const auto & constant_expressions = getParam<std::vector<std::string>>("constant_expressions");
210 for (const auto i : make_range(3))
211 {
212 _displacement[i] = std::make_shared<SymFunction>();
214 getParam<ParsedFunctionExpression>(_disp_name[i]),
215 symbols,
216 constant_names,
217 constant_expressions,
218 comm());
219 }
220
221 _func_params.resize(_coupled_vars.size() + _functions.size() + _postprocessors.size() +
222 _functors.size() + 4);
223
224 // Set up the optional output aux variables (created by the user in the input).
227
230 "original_coordinate_variables",
231 getParam<std::vector<AuxVariableName>>("original_coordinate_variables"),
233
236 "parsed_displacement_variables",
237 getParam<std::vector<AuxVariableName>>("parsed_displacement_variables"),
239
241 {
242 const auto name = getParam<AuxVariableName>("density_factor_variable");
244 paramError("density_factor_variable",
245 "No auxiliary variable named '",
246 name,
247 "' was found. Create an elemental (family = MONOMIAL, order = CONSTANT) auxiliary "
248 "variable with that name.");
250 if (var.isNodal())
251 paramError("density_factor_variable",
252 "Aux variable '",
253 name,
254 "' must be an elemental variable (e.g. family = MONOMIAL, order = CONSTANT).");
257 }
258}
259
260void
262 const std::string & param_name,
263 const std::vector<AuxVariableName> & names,
264 std::vector<unsigned int> & var_numbers)
265{
266 if (names.size() != 3)
267 paramError(param_name,
268 "Exactly three variable names must be provided, for the x, y, and z components.");
269
270 for (const auto & name : names)
271 {
273 paramError(param_name,
274 "No auxiliary variable named '",
275 name,
276 "' was found. Create a nodal (e.g. LAGRANGE) auxiliary variable with that name.");
278 if (!var.isNodal())
279 paramError(param_name, "Auxiliary variable '", name, "' must be a nodal variable.");
280 var_numbers.push_back(var.number());
281 }
282}
283
284void
286{
287 moveNodes();
288
289 // Optionally notify the problem that the mesh changed so dependent systems update.
290 // This object does not respond to meshChanged() (it modifies the mesh actively), so
291 // the resulting broadcast does not call back into it and no guard is needed.
294 /*intermediate_change=*/false, /*contract_mesh=*/false, /*clean_refinement_flags=*/false);
295}
296
297void
299{
300 // Record the original (undisplaced) coordinate-aware element volumes once, on the
301 // first execution while the mesh is still in its reference state. These are
302 // compared against the post-move volumes to form the density adjustment factor.
304 {
305 for (const auto * const elem : _mesh.getMesh().active_local_element_ptr_range())
306 _original_volume[elem->id()] = _assembly->elementVolume(elem);
308 }
309}
310
311void
313{
314 prepare();
315
316 auto & mesh = _mesh.getMesh();
317
318 // A coupled variable can only be read at a node this rank owns, because every degree of
319 // freedom of a node belongs to that node's owner. A DistributedMesh likewise gives a rank
320 // only part of the mesh. In both cases each rank displaces just its own nodes and the
321 // positions are synchronized afterwards. On a ReplicatedMesh with no coupled variables
322 // every rank can evaluate every node identically, which is cheaper than communicating
323 // one position per node.
324 const bool owner_only = !_coupled_vars.empty() || !mesh.is_replicated();
325
326 // Displace nodes on the requested boundaries.
327 for (const auto & boundary_id : _boundary_ids)
328 for (const auto & node_id : _mesh.getNodeList(boundary_id))
329 if (Node * const node = mesh.query_node_ptr(node_id))
330 displaceNode(*node, owner_only);
331
332 // Displace nodes by block. When the user specifies neither 'block' nor 'boundary',
333 // operate on all blocks. Iterate the selected subdomains' elements (including
334 // ghosted elements) and displace their nodes; this is safe on a DistributedMesh
335 // and moves ghosted node copies consistently on every rank that holds them.
336 const bool all_blocks = !isParamSetByUser("block") && !isParamSetByUser("boundary");
337 if (all_blocks || !_subdomain_ids.empty())
338 {
339 const std::set<SubdomainID> subdomains(_subdomain_ids.begin(), _subdomain_ids.end());
340 std::set<dof_id_type> displaced_nodes;
341 for (auto * const elem : mesh.active_element_ptr_range())
342 {
343 if (!all_blocks && !subdomains.count(elem->subdomain_id()))
344 continue;
345 for (auto & node : elem->node_ref_range())
346 if (displaced_nodes.insert(node.id()).second)
347 displaceNode(node, owner_only);
348 }
349 }
350
351 // Each rank displaced only the nodes it owns, so copy every node's position from its
352 // owning rank to every other rank holding a copy. This is the pattern libMesh itself
353 // uses after moving nodes (LaplaceMeshSmoother::smooth, FEMSystem::mesh_position_set).
354 if (owner_only)
355 {
356 libMesh::SyncNodalPositions sync_positions(mesh);
357 libMesh::Parallel::sync_dofobject_data_by_id(
358 mesh.comm(), mesh.nodes_begin(), mesh.nodes_end(), sync_positions);
359 }
360
361 writeOutputs();
362}
363
364void
365MoveNodesByParsedExpressionModifier::displaceNode(Node & node, const bool owner_only)
366{
367 // Capture the original position on first touch (covers nodes created by adaptivity).
368 auto [it, _] = _original_position.try_emplace(node.id(), Point(node));
369 const Point & ref = it->second;
370
371 // The reference position above is recorded on every rank that sees the node, so that it
372 // survives a later repartitioning, but the displacement itself is computed only where it
373 // can be: see the owner_only comment in moveNodes().
374 if (owner_only && node.processor_id() != processor_id())
375 return;
376
377 std::size_t k = 0;
378 for (const auto * const var : _coupled_vars)
379 {
380 const auto sys_num = var->sys().number();
381 // A moved node may carry no DOF for this variable (e.g. the variable is
382 // defined on a different block than the one being moved).
383 if (node.n_dofs(sys_num, var->number()) == 0)
384 mooseError("Coupled variable '",
385 var->name(),
386 "' has no degrees of freedom at node ",
387 node.id(),
388 ". All 'coupled_variables' must be defined on the blocks/boundaries being moved.");
389 const auto dof = node.dof_number(sys_num, var->number(), 0);
390 _func_params[k++] = (*var->sys().currentSolution())(dof);
391 }
392 for (const auto * const func : _functions)
393 _func_params[k++] = func->value(_t, ref);
394 for (const auto * const pp : _postprocessors)
395 _func_params[k++] = *pp;
396 if (!_functors.empty())
397 {
398 // Functors are sampled at the node itself, so put it back in its reference position before
399 // evaluating them. Otherwise a second execution would sample them at the already displaced
400 // location, inconsistent with the 'functions' and 'x', 'y', 'z' symbols.
401 for (const auto i : make_range(Moose::dim))
402 node(i) = ref(i);
404 const auto state = determineState();
405 for (const auto * const functor : _functors)
406 _func_params[k++] = (*functor)(node_arg, state);
407 }
408 _func_params[k++] = ref(0);
409 _func_params[k++] = ref(1);
410 _func_params[k++] = ref(2);
411 _func_params[k++] = _t;
412
413 for (const auto i : make_range(3))
414 node(i) = ref(i) + evaluate(_displacement[i], _disp_name[i]);
415}
416
417void
419{
421 return;
422
423 auto & mesh = _mesh.getMesh();
424 auto & aux_solution = _fe_problem.getAuxiliarySystem().solution();
425
426 // Nodal outputs: original coordinates and/or displacement. The _original_position
427 // map holds every node that was moved; write the value for the nodes this rank
428 // owns (the dof of a nodal variable belongs to the node's owner).
430 for (const auto & [node_id, original] : _original_position)
431 {
432 const Node * const node = mesh.query_node_ptr(node_id);
433 if (!node || node->processor_id() != processor_id())
434 continue;
435 for (const auto i : make_range(Moose::dim))
436 {
438 aux_solution.set(node->dof_number(_aux_sys_num, _coordinate_var[i], 0), original(i));
440 aux_solution.set(node->dof_number(_aux_sys_num, _displacement_var[i], 0),
441 (*node)(i)-original(i));
442 }
443 }
444
445 // Elemental output: density adjustment factor = original volume / current volume.
447 for (auto * const elem : mesh.active_local_element_ptr_range())
448 {
449 const Real v_new = _assembly->elementVolume(elem);
450 const Real factor = (v_new != 0.0) ? _original_volume[elem->id()] / v_new : 1.0;
451 aux_solution.set(elem->dof_number(_aux_sys_num, _density_factor_var, 0), factor);
452 }
453
454 aux_solution.close();
455}
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
registerMooseObject("MooseApp", MoveNodesByParsedExpressionModifier)
Real elementVolume(const Elem *elem) const
On-demand computation of volume element accounting for RZ/RSpherical.
Definition Assembly.C:3755
A MultiMooseEnum object to hold "execute_on" flags.
AuxiliarySystem & getAuxiliarySystem()
virtual Assembly & assembly(const THREAD_ID tid, const unsigned int sys_num) override
virtual void meshChanged(bool intermediate_change, bool contract_mesh, bool clean_refinement_flags)
Update data after a mesh change.
const Function & getFunctionByName(const FunctionName &name) const
Get a function with a given name.
std::vector< GenericReal< is_ad > > _func_params
Array to stage the parameters passed to the functions when calling Eval.
void parsedFunctionSetup(SymFunctionPtr &function, const std::string &expression, const std::string &variables, const std::vector< std::string > &constant_names, const std::vector< std::string > &constant_expressions, const libMesh::Parallel::Communicator &comm) const
Performs setup steps on a SymFunction.
GenericReal< is_ad > evaluate(SymFunctionPtr &, const std::string &object_name="")
Evaluate FParser object and check EvalError.
static InputParameters validParams()
static InputParameters validParams()
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object.
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.
T & set(const std::string &name, bool quiet_mode=false)
Returns a writable reference to the named parameters.
const std::string & name() const
Get the name of the class.
Definition MooseBase.h:103
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition MooseBase.h:457
bool isParamSetByUser(const std::string &name) const
Test if the supplied parameter is set by a user, as opposed to not set or set to default.
Definition MooseBase.h:205
const T & getParam(const std::string &name) const
Retrieve a parameter for the object.
Definition MooseBase.h:406
MeshBase & getMesh()
Accessor for the underlying libMesh Mesh object.
Definition MooseMesh.C:3557
const std::vector< dof_id_type > & getNodeList(boundary_id_type nodeset_id) const
Return a writable reference to a vector of node IDs that belong to nodeset_id.
Definition MooseMesh.C:3579
unsigned int number() const
Get variable number coming from libMesh.
bool isNodal() const override
Is this variable nodal.
MeshModifier that actively displaces the selected nodes by three parsed expressions (x,...
std::unordered_map< dof_id_type, Point > & _original_position
Original (reference) position of each node, captured on first touch.
MooseMesh & _mesh
Reference to the current simulation mesh.
const bool _output_coordinates
Optional outputs, each enabled only when its parameter is provided.
const std::vector< SubdomainID > _subdomain_ids
Blocks whose nodes are displaced.
SymFunctionPtr _displacement[3]
The three parsed displacement functions (x, y, z)
const std::vector< BoundaryID > _boundary_ids
Boundaries whose nodes are displaced.
std::vector< const PostprocessorValue * > _postprocessors
Postprocessor values referenced in the expressions, in symbol order.
unsigned int _aux_sys_num
Auxiliary system number (set when any output is enabled)
MoveNodesByParsedExpressionModifier(const InputParameters &parameters)
void displaceNode(Node &node, bool owner_only)
Displace a single node by the parsed expressions, relative to its original position.
std::vector< unsigned int > _coordinate_var
Aux variable numbers of the original-coordinate components (x, y, z)
std::vector< const Function * > _functions
Functions referenced in the expressions, in symbol order.
std::vector< const Moose::Functor< Real > * > _functors
Functors referenced in the expressions, in symbol order.
const bool _notify_mesh_changed
Whether to notify the problem that the mesh changed after moving nodes.
std::vector< const MooseVariable * > _coupled_vars
Coupled (nodal) variables referenced in the expressions, in symbol order.
unsigned int _density_factor_var
Aux variable number of the per-element density adjustment factor.
static const std::string _disp_name[3]
Parameter names of the three displacement expressions (x, y, z)
void setupNodalOutputVariables(const std::string &param_name, const std::vector< AuxVariableName > &names, std::vector< unsigned int > &var_numbers)
Validate a list of three nodal output aux variable names (exactly three, each existing and nodal) and...
void writeOutputs()
Write the optional original-coordinate, displacement, and density-factor aux variables.
bool & _original_volume_recorded
Whether the original element volumes have been recorded yet.
std::unordered_map< dof_id_type, Real > & _original_volume
Original (undisplaced) coordinate-aware element volumes, captured once.
Assembly * _assembly
Assembly used to compute coordinate-aware element volumes (density factor)
void moveNodes()
Displace all selected nodes (boundary/block restricted, or all blocks)
std::vector< unsigned int > _displacement_var
Aux variable numbers of the displacement components (x, y, z)
void prepare()
Capture the original element volumes (once) before moving any nodes.
virtual void execute() override
Execute method.
An interface for accessing Moose::Functors for systems that do not care about automatic differentiati...
static InputParameters validParams()
virtual const PostprocessorValue & getPostprocessorValueByName(const PostprocessorName &name) const
Retrieve the value of the Postprocessor.
virtual MooseVariable & getStandardVariable(const THREAD_ID tid, const std::string &var_name)=0
Returns the variable reference for requested MooseVariable which may be in any system.
virtual bool hasVariable(const std::string &var_name) const =0
Whether or not this problem has the variable.
unsigned int number() const
Gets the number of this system.
NumericVector< Number > & solution()
Definition SystemBase.h:203
Moose::StateArg determineState() const
Create a functor state argument that corresponds to the implicit state of this object.
SubProblem & _subproblem
Reference to the Subproblem for this user object.
FEProblemBase & _fe_problem
Reference to the FEProblemBase for this user object.
const THREAD_ID _tid
Thread ID of this postprocessor.
processor_id_type processor_id() const
const Parallel::Communicator & comm() const
MeshBase & mesh
static constexpr std::size_t dim
This is the dimension of all vector and tensor datastructures used in MOOSE.
Definition Moose.h:175
static const std::set< SubdomainID > undefined_subdomain_connection
A static member that can be used when the connection of a node to subdomains is unknown.