Line data Source code
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 :
10 : // MOOSE includes
11 : #include "MultiAppConservativeTransfer.h"
12 : #include "MooseTypes.h"
13 : #include "FEProblem.h"
14 : #include "MultiApp.h"
15 : #include "MooseMesh.h"
16 : #include "UserObject.h"
17 : #include "NearestPointIntegralVariablePostprocessor.h"
18 : #include "SystemBase.h"
19 :
20 : InputParameters
21 55339 : MultiAppConservativeTransfer::validParams()
22 : {
23 55339 : InputParameters params = MultiAppFieldTransfer::validParams();
24 221356 : params.addRequiredParam<std::vector<AuxVariableName>>(
25 : "variable", "The auxiliary variable to store the transferred values in.");
26 221356 : params.addRequiredParam<std::vector<VariableName>>("source_variable",
27 : "The variable to transfer from.");
28 :
29 221356 : params.addParam<std::vector<PostprocessorName>>(
30 : "from_postprocessors_to_be_preserved",
31 : "The name of the Postprocessor in the from-app to evaluate an adjusting factor.");
32 :
33 221356 : params.addParam<std::vector<PostprocessorName>>(
34 : "to_postprocessors_to_be_preserved",
35 : {},
36 : "The name of the Postprocessor in the to-app to evaluate an adjusting factor.");
37 166017 : params.addParam<bool>("allow_skipped_adjustment",
38 110678 : false,
39 : "If set to true, the transfer skips adjustment when from or to "
40 : "postprocessor values are either zero or have different signs. If set to "
41 : "false, an error is thrown when encountering these conditions.");
42 166017 : params.addParamNamesToGroup("from_postprocessors_to_be_preserved "
43 : "to_postprocessors_to_be_preserved allow_skipped_adjustment",
44 : "Conservative transfer");
45 :
46 55339 : return params;
47 0 : }
48 :
49 9302 : MultiAppConservativeTransfer::MultiAppConservativeTransfer(const InputParameters & parameters)
50 : : MultiAppFieldTransfer(parameters),
51 35450 : _from_var_names(isParamValid("source_variable")
52 9302 : ? getParam<std::vector<VariableName>>("source_variable")
53 : : std::vector<VariableName>()),
54 18604 : _to_var_names(getParam<std::vector<AuxVariableName>>("variable")),
55 18604 : _preserve_transfer(isParamValid("from_postprocessors_to_be_preserved")),
56 9494 : _from_postprocessors_to_be_preserved(
57 9302 : _preserve_transfer
58 9302 : ? getParam<std::vector<PostprocessorName>>("from_postprocessors_to_be_preserved")
59 : : std::vector<PostprocessorName>{}),
60 18604 : _to_postprocessors_to_be_preserved(
61 : getParam<std::vector<PostprocessorName>>("to_postprocessors_to_be_preserved")),
62 9302 : _use_nearestpoint_pps(false),
63 37208 : _allow_skipped_adjustment(getParam<bool>("allow_skipped_adjustment"))
64 : {
65 9302 : if (_directions.size() != 1)
66 0 : paramError("direction", "This transfer is only unidirectional");
67 :
68 9302 : if (_preserve_transfer)
69 : {
70 : /*
71 : * Not sure how important to support multi variables
72 : * Let us handle the single variable case only right now if the conservative capability is on
73 : */
74 192 : if (_to_var_names.size() != 1)
75 0 : paramError("variable",
76 : " Support single variable only when the conservative capability is on ");
77 :
78 192 : if (_current_direction == TO_MULTIAPP)
79 : {
80 120 : if (_from_postprocessors_to_be_preserved.size() != getToMultiApp()->numGlobalApps() &&
81 27 : _from_postprocessors_to_be_preserved.size() != 1)
82 0 : paramError("from_postprocessors_to_be_preserved",
83 : "Number of from-postprocessors should equal to the number of subapps, or use "
84 : "NearestPointIntegralVariablePostprocessor");
85 93 : if (_to_postprocessors_to_be_preserved.size() != 1)
86 0 : paramError("to_postprocessors_to_be_preserved",
87 : "Number of to-postprocessors should equal to 1");
88 : }
89 99 : else if (_current_direction == FROM_MULTIAPP)
90 : {
91 99 : if (_from_postprocessors_to_be_preserved.size() != 1)
92 0 : paramError("from_postprocessors_to_be_preserved",
93 : "Number of from Postprocessors should equal to 1");
94 :
95 174 : if (_to_postprocessors_to_be_preserved.size() != getFromMultiApp()->numGlobalApps() &&
96 75 : _to_postprocessors_to_be_preserved.size() != 1)
97 0 : paramError("to_postprocessors_to_be_preserved",
98 : "_to_postprocessors_to_be_preserved",
99 : "Number of to Postprocessors should equal to the number of subapps, or use "
100 : "NearestPointIntegralVariablePostprocessor ");
101 : }
102 : }
103 :
104 : /* Have to specify at least one to-variable */
105 9302 : if (_to_var_names.size() == 0)
106 0 : paramError("variable", "You need to specify at least one variable");
107 :
108 : /* Right now, most of transfers support one variable only */
109 9302 : if (_to_var_names.size() == 1)
110 9107 : _to_var_name = _to_var_names[0];
111 :
112 9302 : if (_from_var_names.size() == 1)
113 6674 : _from_var_name = _from_var_names[0];
114 9302 : }
115 :
116 : void
117 9125 : MultiAppConservativeTransfer::initialSetup()
118 : {
119 9125 : MultiAppFieldTransfer::initialSetup();
120 9125 : if (_preserve_transfer)
121 : {
122 192 : if (_from_postprocessors_to_be_preserved.size() == 1 && _current_direction == TO_MULTIAPP)
123 : {
124 78 : FEProblemBase & from_problem = getToMultiApp()->problemBase();
125 78 : auto * pps = dynamic_cast<const NearestPointIntegralVariablePostprocessor *>(
126 78 : &(from_problem.getUserObjectBase(_from_postprocessors_to_be_preserved[0])));
127 78 : if (pps)
128 27 : _use_nearestpoint_pps = true;
129 : else
130 : {
131 51 : _use_nearestpoint_pps = false;
132 51 : if (getToMultiApp()->numGlobalApps() > 1)
133 0 : mooseError(
134 : " You have to specify ",
135 0 : getToMultiApp()->numGlobalApps(),
136 : " regular from-postprocessors, or use NearestPointIntegralVariablePostprocessor ");
137 : }
138 : }
139 :
140 192 : if (_to_postprocessors_to_be_preserved.size() == 1 && _current_direction == FROM_MULTIAPP)
141 : {
142 84 : FEProblemBase & to_problem = getFromMultiApp()->problemBase();
143 84 : auto * pps = dynamic_cast<const NearestPointIntegralVariablePostprocessor *>(
144 84 : &(to_problem.getUserObjectBase(_to_postprocessors_to_be_preserved[0])));
145 84 : if (pps)
146 75 : _use_nearestpoint_pps = true;
147 : else
148 : {
149 9 : _use_nearestpoint_pps = false;
150 9 : if (getFromMultiApp()->numGlobalApps() > 1)
151 0 : mooseError(
152 : " You have to specify ",
153 0 : getFromMultiApp()->numGlobalApps(),
154 : " regular to-postprocessors, or use NearestPointIntegralVariablePostprocessor ");
155 : }
156 : }
157 :
158 192 : const auto multi_app = hasFromMultiApp() ? getFromMultiApp() : getToMultiApp();
159 :
160 : // Let us check execute_on here. Users need to specify execute_on='transfer' in their input
161 : // files for the postprocessors that are used to compute the quantities to conserve in the
162 : // Parent app
163 192 : FEProblemBase & parent_problem = multi_app->problemBase();
164 192 : std::vector<PostprocessorName> pps_empty;
165 : // PPs for parent app
166 : auto & parent_app_pps =
167 192 : _current_direction == TO_MULTIAPP ? pps_empty : _to_postprocessors_to_be_preserved;
168 300 : for (auto & pp : parent_app_pps)
169 : {
170 : // Get out all execute_on options for parent app source pp
171 114 : auto & execute_on = parent_problem.getUserObjectBase(pp).getExecuteOnEnum();
172 114 : const auto & type = parent_problem.getUserObjectBase(pp).type();
173 : // Check if parent app has transfer execute_on
174 114 : if (!execute_on.isValueSet(EXEC_TRANSFER))
175 6 : mooseError(
176 6 : "execute_on='transfer' is required in the conservative transfer for " + type + " '",
177 : pp,
178 : "' computed in the parent application.\n"
179 : "Please add execute_on='transfer' to this postprocessor in the input file.\n"
180 : "For a custom postprocessor, make sure that execute_on options are not hardcoded.");
181 : }
182 :
183 : // Sub apps
184 495 : for (unsigned int i = 0; i < multi_app->numGlobalApps(); i++)
185 : {
186 : // If we do not have this app, we skip
187 312 : if (!multi_app->hasLocalApp(i))
188 60 : continue;
189 : // Sub problem for
190 252 : FEProblemBase & sub_problem = multi_app->appProblemBase(i);
191 : // PPs for this subapp
192 : auto & sub_pps =
193 252 : _current_direction == TO_MULTIAPP ? _to_postprocessors_to_be_preserved : pps_empty;
194 366 : for (auto & sub_pp : sub_pps)
195 : {
196 : // Get out of all execute_on options for sub pp
197 117 : auto & execute_on = sub_problem.getUserObjectBase(sub_pp).getExecuteOnEnum();
198 117 : const auto & type = sub_problem.getUserObjectBase(sub_pp).type();
199 : // Check if sub pp has transfer execute_on
200 117 : if (!execute_on.isValueSet(EXEC_TRANSFER))
201 3 : mooseError(
202 3 : "execute_on='transfer' is required in the conservative transfer for " + type + " '",
203 : sub_pp,
204 3 : "' in child application '" + multi_app->name() +
205 : "'. \n"
206 : "Please add execute_on='transfer' to this postprocessor in the input file.\n"
207 : "For a custom postprocessor, make sure that execute_on options are not "
208 : "hardcoded.");
209 : }
210 : }
211 183 : }
212 9116 : }
213 :
214 : void
215 55280 : MultiAppConservativeTransfer::postExecute()
216 : {
217 55280 : if (_preserve_transfer)
218 : {
219 925 : TIME_SECTION("MultiAppConservativeTransfer::execute()",
220 : 5,
221 : "Post transfer to preserve postprocessor values");
222 :
223 185 : if (_current_direction == TO_MULTIAPP)
224 : {
225 77 : FEProblemBase & from_problem = getToMultiApp()->problemBase();
226 77 : if (_use_nearestpoint_pps)
227 44 : from_problem.computeUserObjectByName(
228 22 : EXEC_TRANSFER, Moose::POST_AUX, _from_postprocessors_to_be_preserved[0]);
229 :
230 187 : for (unsigned int i = 0; i < getToMultiApp()->numGlobalApps(); i++)
231 110 : if (getToMultiApp()->hasLocalApp(i))
232 : {
233 92 : if (_use_nearestpoint_pps)
234 64 : adjustTransferredSolutionNearestPoint(i,
235 : &from_problem,
236 32 : _from_postprocessors_to_be_preserved[0],
237 64 : getToMultiApp()->appProblemBase(i),
238 32 : _to_postprocessors_to_be_preserved[0]);
239 : else
240 120 : adjustTransferredSolution(&from_problem,
241 60 : _from_postprocessors_to_be_preserved[i],
242 120 : getToMultiApp()->appProblemBase(i),
243 60 : _to_postprocessors_to_be_preserved[0]);
244 : }
245 : }
246 :
247 108 : else if (_current_direction == FROM_MULTIAPP)
248 : {
249 108 : FEProblemBase & to_problem = getFromMultiApp()->problemBase();
250 108 : if (_use_nearestpoint_pps)
251 140 : to_problem.computeUserObjectByName(
252 70 : EXEC_TRANSFER, Moose::POST_AUX, _to_postprocessors_to_be_preserved[0]);
253 :
254 297 : for (unsigned int i = 0; i < getFromMultiApp()->numGlobalApps(); i++)
255 : {
256 189 : if (_use_nearestpoint_pps)
257 140 : adjustTransferredSolutionNearestPoint(
258 : i,
259 280 : getFromMultiApp()->hasLocalApp(i) ? &getFromMultiApp()->appProblemBase(i) : nullptr,
260 140 : _from_postprocessors_to_be_preserved[0],
261 : to_problem,
262 140 : _to_postprocessors_to_be_preserved[0]);
263 : else
264 49 : adjustTransferredSolution(
265 98 : getFromMultiApp()->hasLocalApp(i) ? &getFromMultiApp()->appProblemBase(i) : nullptr,
266 49 : _from_postprocessors_to_be_preserved[0],
267 : to_problem,
268 49 : _to_postprocessors_to_be_preserved[i]);
269 : }
270 :
271 : // Compute the to-postprocessor again so that it has the right value with the updated solution
272 108 : if (_use_nearestpoint_pps)
273 140 : to_problem.computeUserObjectByName(
274 70 : EXEC_TRANSFER, Moose::POST_AUX, _to_postprocessors_to_be_preserved[0]);
275 : }
276 185 : }
277 55280 : }
278 :
279 : void
280 172 : MultiAppConservativeTransfer::adjustTransferredSolutionNearestPoint(
281 : unsigned int i,
282 : FEProblemBase * from_problem,
283 : PostprocessorName & from_postprocessor,
284 : FEProblemBase & to_problem,
285 : PostprocessorName & to_postprocessor)
286 : {
287 172 : PostprocessorValue from_adjuster = 0;
288 172 : if (from_problem && _current_direction == FROM_MULTIAPP)
289 104 : from_adjuster = from_problem->getPostprocessorValueByName(from_postprocessor);
290 : else
291 68 : from_adjuster = 0;
292 :
293 : /* Everyone on the parent application side should know this value; use it to scale the solution */
294 172 : if (_current_direction == FROM_MULTIAPP)
295 : {
296 : /* In this case, only one subapp has value, and other subapps' must be zero.
297 : * We should see the maximum value.
298 : */
299 140 : PostprocessorValue from_adjuster_tmp = from_adjuster;
300 140 : comm().max(from_adjuster);
301 :
302 : /* We may have a negative value */
303 140 : if (MooseUtils::absoluteFuzzyLessEqual(from_adjuster, 0.))
304 : {
305 0 : comm().min(from_adjuster_tmp);
306 0 : from_adjuster = from_adjuster_tmp;
307 : }
308 : }
309 :
310 172 : PostprocessorValue to_adjuster = 0;
311 : // Compute to-postprocessor to have the adjuster
312 172 : if (_current_direction == TO_MULTIAPP)
313 : {
314 32 : to_problem.computeUserObjectByName(EXEC_TRANSFER, Moose::POST_AUX, to_postprocessor);
315 32 : to_adjuster = to_problem.getPostprocessorValueByName(to_postprocessor);
316 : }
317 :
318 172 : auto & to_var = to_problem.getVariable(
319 : 0, _to_var_name, Moose::VarKindType::VAR_ANY, Moose::VarFieldType::VAR_FIELD_STANDARD);
320 172 : auto & to_sys = to_var.sys().system();
321 172 : auto var_num = to_sys.variable_number(_to_var_name);
322 172 : auto sys_num = to_sys.number();
323 : auto & pps = static_cast<const NearestPointIntegralVariablePostprocessor &>(
324 312 : _current_direction == FROM_MULTIAPP ? (to_problem.getUserObjectBase(to_postprocessor))
325 172 : : (from_problem->getUserObjectBase(from_postprocessor)));
326 172 : auto & to_solution = to_var.sys().solution();
327 172 : auto & to_mesh = to_problem.mesh().getMesh();
328 172 : bool is_nodal = to_sys.variable_type(var_num).family == LAGRANGE;
329 172 : if (is_nodal)
330 : {
331 682404 : for (const auto & node : to_mesh.local_node_ptr_range())
332 : {
333 : // Skip this node if the variable has no dofs at it.
334 341140 : if (node->n_dofs(sys_num, var_num) < 1)
335 3200 : continue;
336 :
337 337940 : Real scale = 1;
338 337940 : if (_current_direction == FROM_MULTIAPP)
339 : {
340 337268 : auto ii = pps.nearestPointIndex(*node);
341 337268 : if (ii != i || !performAdjustment(from_adjuster, pps.userObjectValue(i)))
342 168634 : continue;
343 :
344 168634 : scale = from_adjuster / pps.userObjectValue(i);
345 : }
346 : else
347 : {
348 672 : if (!performAdjustment(pps.userObjectValue(i), to_adjuster))
349 0 : continue;
350 :
351 672 : scale = pps.userObjectValue(i) / to_adjuster;
352 : }
353 :
354 : /* Need to scale this node */
355 169306 : dof_id_type dof = node->dof_number(sys_num, var_num, 0);
356 169306 : to_solution.set(dof, scale * to_solution(dof));
357 124 : }
358 : }
359 : else
360 : {
361 576048 : for (auto & elem : as_range(to_mesh.local_elements_begin(), to_mesh.local_elements_end()))
362 : {
363 : // Skip this element if the variable has no dofs at it.
364 288000 : if (elem->n_dofs(sys_num, var_num) < 1)
365 0 : continue;
366 :
367 288000 : Real scale = 1;
368 288000 : if (_current_direction == FROM_MULTIAPP)
369 : {
370 288000 : unsigned int ii = pps.nearestPointIndex(elem->vertex_average());
371 288000 : if (ii != i || !performAdjustment(from_adjuster, pps.userObjectValue(i)))
372 144000 : continue;
373 :
374 144000 : scale = from_adjuster / pps.userObjectValue(i);
375 : }
376 : else
377 : {
378 0 : if (!performAdjustment(pps.userObjectValue(i), to_adjuster))
379 0 : continue;
380 :
381 0 : scale = pps.userObjectValue(i) / to_adjuster;
382 : }
383 :
384 144000 : dof_id_type dof = elem->dof_number(sys_num, var_num, 0);
385 144000 : to_solution.set(dof, scale * to_solution(dof));
386 48 : }
387 : }
388 :
389 172 : to_solution.close();
390 172 : to_sys.update();
391 :
392 : // Compute the to-postprocessor again so that it has the right value with the updated solution
393 172 : if (_current_direction == TO_MULTIAPP)
394 32 : to_problem.computeUserObjectByName(EXEC_TRANSFER, Moose::POST_AUX, to_postprocessor);
395 172 : }
396 :
397 : void
398 109 : MultiAppConservativeTransfer::adjustTransferredSolution(FEProblemBase * from_problem,
399 : PostprocessorName & from_postprocessor,
400 : FEProblemBase & to_problem,
401 : PostprocessorName & to_postprocessor)
402 : {
403 109 : PostprocessorValue from_adjuster = 0;
404 109 : if (from_problem)
405 103 : from_adjuster = from_problem->getPostprocessorValueByName(from_postprocessor);
406 : else
407 6 : from_adjuster = 0;
408 :
409 : /* Everyone on the parent side should know this value; use it to scale the solution */
410 109 : if (_current_direction == FROM_MULTIAPP)
411 : {
412 : /* In this case, only one subapp has value, and other subapps' must be zero.
413 : * We should see the maximum value.
414 : */
415 49 : PostprocessorValue from_adjuster_tmp = from_adjuster;
416 49 : comm().max(from_adjuster);
417 :
418 : /* We may have a negative value, and let us try it again */
419 49 : if (MooseUtils::absoluteFuzzyLessEqual(from_adjuster, 0.))
420 : {
421 0 : comm().min(from_adjuster_tmp);
422 0 : from_adjuster = from_adjuster_tmp;
423 : }
424 : }
425 :
426 : // Compute to-postprocessor to have the adjuster
427 109 : to_problem.computeUserObjectByName(EXEC_TRANSFER, Moose::POST_AUX, to_postprocessor);
428 :
429 : // Now we should have the right adjuster based on the transferred solution
430 109 : const auto to_adjuster = to_problem.getPostprocessorValueByName(to_postprocessor);
431 :
432 : // decide if the adjustment should be performed
433 109 : if (!performAdjustment(from_adjuster, to_adjuster))
434 11 : return;
435 :
436 98 : auto & to_var = to_problem.getVariable(
437 : 0, _to_var_name, Moose::VarKindType::VAR_ANY, Moose::VarFieldType::VAR_FIELD_STANDARD);
438 98 : auto & to_sys = to_var.sys().system();
439 98 : auto var_num = to_sys.variable_number(_to_var_name);
440 98 : auto sys_num = to_sys.number();
441 : auto * pps =
442 98 : dynamic_cast<const BlockRestrictable *>(&(to_problem.getUserObjectBase(to_postprocessor)));
443 98 : auto & to_solution = to_var.sys().solution();
444 98 : auto & to_mesh = to_problem.mesh().getMesh();
445 98 : auto & moose_mesh = to_problem.mesh();
446 98 : bool is_nodal = to_sys.variable_type(var_num).family == LAGRANGE;
447 98 : if (is_nodal)
448 : {
449 7449 : for (const auto & node : to_mesh.local_node_ptr_range())
450 : {
451 : // Skip this node if the variable has no dofs at it.
452 7400 : if (node->n_dofs(sys_num, var_num) < 1)
453 1600 : continue;
454 :
455 5800 : bool scale_current_node = false;
456 : /* If we care about block IDs */
457 5800 : if (pps)
458 : {
459 5800 : auto & blockids = pps->blockIDs();
460 5800 : auto & node_to_elem_map = moose_mesh.nodeToElemMap();
461 5800 : auto neighbor_elements = node_to_elem_map.find(node->id());
462 8985 : for (auto element : neighbor_elements->second)
463 : {
464 8105 : auto & elem = to_mesh.elem_ref(element);
465 8105 : if (blockids.find(elem.subdomain_id()) != blockids.end())
466 : {
467 4920 : scale_current_node = true;
468 4920 : break;
469 : }
470 : }
471 : }
472 : else
473 : {
474 0 : scale_current_node = true;
475 : }
476 : /* Need to scale this node */
477 5800 : if (scale_current_node)
478 : {
479 4920 : dof_id_type dof = node->dof_number(sys_num, var_num, 0);
480 4920 : to_solution.set(dof, (from_adjuster / to_adjuster) * to_solution(dof));
481 : }
482 49 : }
483 : }
484 : else
485 : {
486 6899 : for (auto & elem : as_range(to_mesh.local_elements_begin(), to_mesh.local_elements_end()))
487 : {
488 : // Skip this element if the variable has no dofs at it.
489 6850 : if (elem->n_dofs(sys_num, var_num) < 1)
490 0 : continue;
491 :
492 6850 : bool scale_current_element = false;
493 6850 : if (pps)
494 : {
495 6850 : auto & blockids = pps->blockIDs();
496 6850 : if (blockids.find(elem->subdomain_id()) != blockids.end())
497 : {
498 6634 : scale_current_element = true;
499 : }
500 : }
501 : else
502 : {
503 0 : scale_current_element = true;
504 : }
505 6850 : if (scale_current_element)
506 : {
507 6634 : unsigned int n_comp = elem->n_comp(sys_num, var_num);
508 :
509 22868 : for (unsigned int offset = 0; offset < n_comp; offset++)
510 : {
511 16234 : dof_id_type dof = elem->dof_number(sys_num, var_num, offset);
512 16234 : to_solution.set(dof, (from_adjuster / to_adjuster) * to_solution(dof));
513 : }
514 : }
515 49 : }
516 : }
517 :
518 98 : to_solution.close();
519 98 : to_sys.update();
520 :
521 : // Compute again so that the post-processor has the value with the updated solution
522 98 : to_problem.computeUserObjectByName(EXEC_TRANSFER, Moose::POST_AUX, to_postprocessor);
523 : }
524 :
525 : bool
526 313415 : MultiAppConservativeTransfer::performAdjustment(const PostprocessorValue & from,
527 : const PostprocessorValue & to) const
528 : {
529 313415 : if (from * to > 0)
530 313404 : return true;
531 11 : else if (_allow_skipped_adjustment)
532 11 : return false;
533 : else
534 0 : mooseError("Adjustment postprocessors from: ",
535 : from,
536 : " to: ",
537 : to,
538 : " must both have the same sign and be different from 0");
539 : }
|