Line data Source code
1 : // The libMesh Finite Element Library.
2 : // Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 :
4 : // This library is free software; you can redistribute it and/or
5 : // modify it under the terms of the GNU Lesser General Public
6 : // License as published by the Free Software Foundation; either
7 : // version 2.1 of the License, or (at your option) any later version.
8 :
9 : // This library is distributed in the hope that it will be useful,
10 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : // Lesser General Public License for more details.
13 :
14 : // You should have received a copy of the GNU Lesser General Public
15 : // License along with this library; if not, write to the Free Software
16 : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 :
18 :
19 : // libMesh includes
20 : #include "libmesh/dof_map.h"
21 : #include "libmesh/elem.h"
22 : #include "libmesh/equation_systems.h"
23 : #include "libmesh/fe_base.h"
24 : #include "libmesh/fem_context.h"
25 : #include "libmesh/fem_system.h"
26 : #include "libmesh/libmesh_logging.h"
27 : #include "libmesh/mesh_base.h"
28 : #include "libmesh/numeric_vector.h"
29 : #include "libmesh/parallel_algebra.h"
30 : #include "libmesh/parallel_ghost_sync.h"
31 : #include "libmesh/quadrature.h"
32 : #include "libmesh/sparse_matrix.h"
33 : #include "libmesh/time_solver.h"
34 : #include "libmesh/unsteady_solver.h" // For eulerian_residual
35 : #include "libmesh/fe_interface.h"
36 :
37 : namespace {
38 : using namespace libMesh;
39 :
40 : typedef Threads::spin_mutex femsystem_mutex;
41 : femsystem_mutex assembly_mutex;
42 :
43 21266191 : void assemble_unconstrained_element_system(const FEMSystem & _sys,
44 : const bool _get_jacobian,
45 : const bool _constrain_heterogeneously,
46 : FEMContext & _femcontext)
47 : {
48 21266191 : if (_sys.print_element_solutions)
49 : {
50 0 : std::streamsize old_precision = libMesh::out.precision();
51 0 : libMesh::out.precision(16);
52 0 : if (_femcontext.has_elem())
53 0 : libMesh::out << "U_elem " << _femcontext.get_elem().id();
54 : else
55 0 : libMesh::out << "U_scalar ";
56 0 : libMesh::out << " = " << _femcontext.get_elem_solution() << std::endl;
57 :
58 0 : if (_sys.use_fixed_solution)
59 : {
60 0 : if (_femcontext.has_elem())
61 0 : libMesh::out << "Ufixed_elem " << _femcontext.get_elem().id();
62 : else
63 0 : libMesh::out << "Ufixed_scalar ";
64 0 : libMesh::out << " = " << _femcontext.get_elem_fixed_solution() << std::endl;
65 0 : libMesh::out.precision(old_precision);
66 : }
67 : }
68 :
69 : // We need jacobians to do heterogeneous residual constraints
70 21266191 : const bool need_jacobian =
71 1898516 : (_get_jacobian || _constrain_heterogeneously);
72 :
73 : bool jacobian_computed =
74 21266191 : _sys.time_solver->element_residual(need_jacobian, _femcontext);
75 :
76 : // Compute a numeric jacobian if we have to
77 21266191 : if (need_jacobian && !jacobian_computed)
78 : {
79 : // Make sure we didn't compute a jacobian and lie about it
80 21556 : libmesh_assert_equal_to (_femcontext.get_elem_jacobian().l1_norm(), 0.0);
81 : // Logging of numerical jacobians is done separately
82 227304 : _sys.numerical_elem_jacobian(_femcontext);
83 : }
84 :
85 : // Compute a numeric jacobian if we're asked to verify the
86 : // analytic jacobian we got
87 21266191 : if (need_jacobian && jacobian_computed &&
88 6252220 : _sys.verify_analytic_jacobians != 0.0)
89 : {
90 0 : DenseMatrix<Number> analytic_jacobian(_femcontext.get_elem_jacobian());
91 :
92 0 : _femcontext.get_elem_jacobian().zero();
93 : // Logging of numerical jacobians is done separately
94 0 : _sys.numerical_elem_jacobian(_femcontext);
95 :
96 0 : Real analytic_norm = analytic_jacobian.l1_norm();
97 0 : Real numerical_norm = _femcontext.get_elem_jacobian().l1_norm();
98 :
99 : // If we can continue, we'll probably prefer the analytic jacobian
100 0 : analytic_jacobian.swap(_femcontext.get_elem_jacobian());
101 :
102 : // The matrix "analytic_jacobian" will now hold the error matrix
103 0 : analytic_jacobian.add(-1.0, _femcontext.get_elem_jacobian());
104 0 : Real error_norm = analytic_jacobian.l1_norm();
105 :
106 0 : Real relative_error = error_norm /
107 0 : std::max(analytic_norm, numerical_norm);
108 :
109 0 : if (relative_error > _sys.verify_analytic_jacobians)
110 : {
111 0 : libMesh::err << "Relative error " << relative_error
112 0 : << " detected in analytic jacobian on element "
113 0 : << _femcontext.get_elem().id() << '!' << std::endl;
114 :
115 0 : std::streamsize old_precision = libMesh::out.precision();
116 0 : libMesh::out.precision(16);
117 0 : libMesh::out << "J_analytic " << _femcontext.get_elem().id() << " = "
118 0 : << _femcontext.get_elem_jacobian() << std::endl;
119 0 : analytic_jacobian.add(1.0, _femcontext.get_elem_jacobian());
120 0 : libMesh::out << "J_numeric " << _femcontext.get_elem().id() << " = "
121 0 : << analytic_jacobian << std::endl;
122 :
123 0 : libMesh::out.precision(old_precision);
124 :
125 0 : libmesh_error_msg("Relative error too large, exiting!");
126 : }
127 0 : }
128 :
129 21266191 : const unsigned char n_sides = _femcontext.get_elem().n_sides();
130 106814939 : for (_femcontext.side = 0; _femcontext.side != n_sides;
131 85548748 : ++_femcontext.side)
132 : {
133 : // Don't compute on non-boundary sides unless requested
134 171097496 : if (!_sys.get_physics()->compute_internal_sides &&
135 85548748 : _femcontext.get_elem().neighbor_ptr(_femcontext.side) != nullptr)
136 82999653 : continue;
137 :
138 : // Any mesh movement has already been done (and restored,
139 : // if the TimeSolver isn't broken), but
140 : // reinitializing the side FE objects is still necessary
141 2549095 : _femcontext.side_fe_reinit();
142 :
143 2993379 : DenseMatrix<Number> old_jacobian;
144 : // If we're in DEBUG mode, we should always verify that the
145 : // user's side_residual function doesn't alter our existing
146 : // jacobian and then lie about it
147 : #ifndef DEBUG
148 : // Even if we're not in DEBUG mode, when we're verifying
149 : // analytic jacobians we'll want to verify each side's
150 : // jacobian contribution separately.
151 2326950 : if (_sys.verify_analytic_jacobians != 0.0 && need_jacobian)
152 : #endif // ifndef DEBUG
153 : {
154 222145 : old_jacobian = _femcontext.get_elem_jacobian();
155 222145 : _femcontext.get_elem_jacobian().zero();
156 : }
157 :
158 : jacobian_computed =
159 2549095 : _sys.time_solver->side_residual(need_jacobian, _femcontext);
160 :
161 : // Compute a numeric jacobian if we have to
162 2549095 : if (need_jacobian && !jacobian_computed)
163 : {
164 : // If we have already backed up old_jacobian,
165 : // we can make sure side_residual didn't compute a
166 : // jacobian and lie about it.
167 : //
168 : // If we haven't, then we need to, to let
169 : // numerical_side_jacobian work.
170 49140 : if (old_jacobian.m())
171 5614 : libmesh_assert_equal_to (_femcontext.get_elem_jacobian().l1_norm(), 0.0);
172 : else
173 : {
174 43526 : old_jacobian = _femcontext.get_elem_jacobian();
175 0 : _femcontext.get_elem_jacobian().zero();
176 : }
177 :
178 : // Logging of numerical jacobians is done separately
179 49140 : _sys.numerical_side_jacobian(_femcontext);
180 :
181 : // Add back in element interior numerical Jacobian
182 43530 : _femcontext.get_elem_jacobian() += old_jacobian;
183 : }
184 :
185 : // Compute a numeric jacobian if we're asked to verify the
186 : // analytic jacobian we got
187 2499955 : else if (need_jacobian && jacobian_computed &&
188 827981 : _sys.verify_analytic_jacobians != 0.0)
189 : {
190 0 : DenseMatrix<Number> analytic_jacobian(_femcontext.get_elem_jacobian());
191 :
192 0 : _femcontext.get_elem_jacobian().zero();
193 : // Logging of numerical jacobians is done separately
194 0 : _sys.numerical_side_jacobian(_femcontext);
195 :
196 0 : Real analytic_norm = analytic_jacobian.l1_norm();
197 0 : Real numerical_norm = _femcontext.get_elem_jacobian().l1_norm();
198 :
199 : // If we can continue, we'll probably prefer the analytic jacobian
200 0 : analytic_jacobian.swap(_femcontext.get_elem_jacobian());
201 :
202 : // The matrix "analytic_jacobian" will now hold the error matrix
203 0 : analytic_jacobian.add(-1.0, _femcontext.get_elem_jacobian());
204 0 : Real error_norm = analytic_jacobian.l1_norm();
205 :
206 0 : Real relative_error = error_norm /
207 0 : std::max(analytic_norm, numerical_norm);
208 :
209 0 : if (relative_error > _sys.verify_analytic_jacobians)
210 : {
211 0 : libMesh::err << "Relative error " << relative_error
212 0 : << " detected in analytic jacobian on element "
213 0 : << _femcontext.get_elem().id()
214 0 : << ", side "
215 0 : << static_cast<unsigned int>(_femcontext.side) << '!' << std::endl;
216 :
217 0 : std::streamsize old_precision = libMesh::out.precision();
218 0 : libMesh::out.precision(16);
219 0 : libMesh::out << "J_analytic " << _femcontext.get_elem().id() << " = "
220 0 : << _femcontext.get_elem_jacobian() << std::endl;
221 0 : analytic_jacobian.add(1.0, _femcontext.get_elem_jacobian());
222 0 : libMesh::out << "J_numeric " << _femcontext.get_elem().id() << " = "
223 0 : << analytic_jacobian << std::endl;
224 0 : libMesh::out.precision(old_precision);
225 :
226 0 : libmesh_error_msg("Relative error too large, exiting!");
227 : }
228 : // Once we've verified a side, we'll want to add back the
229 : // rest of the accumulated jacobian
230 0 : _femcontext.get_elem_jacobian() += old_jacobian;
231 0 : }
232 :
233 : // In DEBUG mode, we've set elem_jacobian == 0, and we
234 : // may have yet to add the old jacobian back
235 : #ifdef DEBUG
236 : else
237 : {
238 216531 : _femcontext.get_elem_jacobian() += old_jacobian;
239 : }
240 : #endif // ifdef DEBUG
241 2104811 : }
242 21266191 : }
243 :
244 21266191 : void add_element_system(FEMSystem & _sys,
245 : const bool _get_residual,
246 : const bool _get_jacobian,
247 : const bool _constrain_heterogeneously,
248 : const bool _no_constraints,
249 : FEMContext & _femcontext)
250 : {
251 : #ifdef LIBMESH_ENABLE_CONSTRAINTS
252 21266191 : if (_get_residual && _sys.print_element_residuals)
253 : {
254 0 : std::streamsize old_precision = libMesh::out.precision();
255 0 : libMesh::out.precision(16);
256 0 : if (_femcontext.has_elem())
257 0 : libMesh::out << "Rraw_elem " << _femcontext.get_elem().id();
258 : else
259 0 : libMesh::out << "Rraw_scalar ";
260 0 : libMesh::out << " = " << _femcontext.get_elem_residual() << std::endl;
261 0 : libMesh::out.precision(old_precision);
262 : }
263 :
264 21266191 : if (_get_jacobian && _sys.print_element_jacobians)
265 : {
266 0 : std::streamsize old_precision = libMesh::out.precision();
267 0 : libMesh::out.precision(16);
268 0 : if (_femcontext.has_elem())
269 0 : libMesh::out << "Jraw_elem " << _femcontext.get_elem().id();
270 : else
271 0 : libMesh::out << "Jraw_scalar ";
272 0 : libMesh::out << " = " << _femcontext.get_elem_jacobian() << std::endl;
273 0 : libMesh::out.precision(old_precision);
274 : }
275 :
276 : // We turn off the asymmetric constraint application iff we expect
277 : // enforce_constraints_exactly() to be called in the solver
278 21266191 : const bool constrain_in_solver = _sys.get_constrain_in_solver();
279 :
280 21266191 : if (_get_residual && _get_jacobian)
281 : {
282 4307655 : if (constrain_in_solver)
283 : {
284 3954495 : if (_constrain_heterogeneously)
285 0 : _sys.get_dof_map().heterogenously_constrain_element_matrix_and_vector
286 0 : (_femcontext.get_elem_jacobian(),
287 : _femcontext.get_elem_residual(),
288 : _femcontext.get_dof_indices(), false);
289 3954495 : else if (!_no_constraints)
290 351240 : _sys.get_dof_map().constrain_element_matrix_and_vector
291 3954495 : (_femcontext.get_elem_jacobian(),
292 : _femcontext.get_elem_residual(),
293 : _femcontext.get_dof_indices(), false);
294 : }
295 1760 : else if (!_no_constraints)
296 160 : _sys.get_dof_map().heterogeneously_constrain_element_jacobian_and_residual
297 1760 : (_femcontext.get_elem_jacobian(),
298 : _femcontext.get_elem_residual(),
299 : _femcontext.get_dof_indices(),
300 160 : *_sys.current_local_solution);
301 : // Do nothing if (_no_constraints)
302 : }
303 17309936 : else if (_get_residual)
304 : {
305 14788427 : if (constrain_in_solver)
306 : {
307 14779011 : if (_constrain_heterogeneously)
308 0 : _sys.get_dof_map().heterogenously_constrain_element_vector
309 0 : (_femcontext.get_elem_jacobian(),
310 : _femcontext.get_elem_residual(),
311 : _femcontext.get_dof_indices(), false);
312 14779011 : else if (!_no_constraints)
313 1305894 : _sys.get_dof_map().constrain_element_vector
314 14639115 : (_femcontext.get_elem_residual(),
315 : _femcontext.get_dof_indices(), false);
316 : }
317 9416 : else if (!_no_constraints)
318 160 : _sys.get_dof_map().heterogeneously_constrain_element_residual
319 1760 : (_femcontext.get_elem_residual(),
320 : _femcontext.get_dof_indices(),
321 160 : *_sys.current_local_solution);
322 : // Do nothing if (_no_constraints)
323 : }
324 2521509 : else if (_get_jacobian)
325 : {
326 : // Heterogeneous and homogeneous constraints are the same on the
327 : // matrix
328 : // Only get these contribs if we are applying some constraints
329 2521509 : if (!_no_constraints)
330 2750043 : _sys.get_dof_map().constrain_element_matrix (_femcontext.get_elem_jacobian(),
331 : _femcontext.get_dof_indices(),
332 2521509 : !constrain_in_solver);
333 : }
334 : #else
335 : libmesh_ignore(_constrain_heterogeneously, _no_constraints);
336 : #endif // #ifdef LIBMESH_ENABLE_CONSTRAINTS
337 :
338 21266191 : if (_get_residual && _sys.print_element_residuals)
339 : {
340 0 : std::streamsize old_precision = libMesh::out.precision();
341 0 : libMesh::out.precision(16);
342 0 : if (_femcontext.has_elem())
343 0 : libMesh::out << "R_elem " << _femcontext.get_elem().id();
344 : else
345 0 : libMesh::out << "R_scalar ";
346 0 : libMesh::out << " = " << _femcontext.get_elem_residual() << std::endl;
347 0 : libMesh::out.precision(old_precision);
348 : }
349 :
350 21266191 : if (_get_jacobian && _sys.print_element_jacobians)
351 : {
352 0 : std::streamsize old_precision = libMesh::out.precision();
353 0 : libMesh::out.precision(16);
354 0 : if (_femcontext.has_elem())
355 0 : libMesh::out << "J_elem " << _femcontext.get_elem().id();
356 : else
357 0 : libMesh::out << "J_scalar ";
358 0 : libMesh::out << " = " << _femcontext.get_elem_jacobian() << std::endl;
359 0 : libMesh::out.precision(old_precision);
360 : }
361 :
362 : { // A lock is necessary around access to the global system
363 3797032 : femsystem_mutex::scoped_lock lock(assembly_mutex);
364 :
365 21266191 : if (_get_jacobian)
366 7057698 : _sys.get_system_matrix().add_matrix (_femcontext.get_elem_jacobian(),
367 1159856 : _femcontext.get_dof_indices());
368 21266191 : if (_get_residual)
369 18744682 : _sys.rhs->add_vector (_femcontext.get_elem_residual(),
370 1669982 : _femcontext.get_dof_indices());
371 : } // Scope for assembly mutex
372 21266191 : }
373 :
374 :
375 :
376 : class AssemblyContributions
377 : {
378 : public:
379 : /**
380 : * constructor to set context
381 : */
382 6268 : AssemblyContributions(FEMSystem & sys,
383 : bool get_residual,
384 : bool get_jacobian,
385 : bool constrain_heterogeneously,
386 218854 : bool no_constraints) :
387 206318 : _sys(sys),
388 206318 : _get_residual(get_residual),
389 206318 : _get_jacobian(get_jacobian),
390 206318 : _constrain_heterogeneously(constrain_heterogeneously),
391 218854 : _no_constraints(no_constraints) {}
392 :
393 : /**
394 : * operator() for use with Threads::parallel_for().
395 : */
396 234976 : void operator()(const ConstElemRange & range) const
397 : {
398 246648 : std::unique_ptr<DiffContext> con = _sys.build_context();
399 11672 : FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
400 234976 : _sys.init_context(_femcontext);
401 :
402 21501167 : for (const auto & elem : range)
403 : {
404 21266191 : _femcontext.pre_fe_reinit(_sys, elem);
405 21266191 : _femcontext.elem_fe_reinit();
406 :
407 : assemble_unconstrained_element_system
408 21266191 : (_sys, _get_jacobian, _constrain_heterogeneously, _femcontext);
409 :
410 : add_element_system
411 25063205 : (_sys, _get_residual, _get_jacobian,
412 21266191 : _constrain_heterogeneously, _no_constraints, _femcontext);
413 : }
414 234976 : }
415 :
416 : private:
417 :
418 : FEMSystem & _sys;
419 :
420 : const bool _get_residual, _get_jacobian, _constrain_heterogeneously, _no_constraints;
421 : };
422 :
423 : class PostprocessContributions
424 : {
425 : public:
426 : /**
427 : * constructor to set context
428 : */
429 : explicit
430 1976 : PostprocessContributions(FEMSystem & sys) : _sys(sys) {}
431 :
432 : /**
433 : * operator() for use with Threads::parallel_for().
434 : */
435 2048 : void operator()(const ConstElemRange & range) const
436 : {
437 2252 : std::unique_ptr<DiffContext> con = _sys.build_context();
438 204 : FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
439 2048 : _sys.init_context(_femcontext);
440 :
441 64800 : for (const auto & elem : range)
442 : {
443 62752 : _femcontext.pre_fe_reinit(_sys, elem);
444 :
445 : // Optionally initialize all the interior FE objects on elem.
446 62752 : if (_sys.fe_reinit_during_postprocess)
447 62752 : _femcontext.elem_fe_reinit();
448 :
449 62752 : _sys.element_postprocess(_femcontext);
450 :
451 62752 : const unsigned char n_sides = _femcontext.get_elem().n_sides();
452 310944 : for (_femcontext.side = 0; _femcontext.side != n_sides;
453 248192 : ++_femcontext.side)
454 : {
455 : // Don't compute on non-boundary sides unless requested
456 269264 : if (!_sys.postprocess_sides ||
457 317088 : (!_sys.get_physics()->compute_internal_sides &&
458 169080 : _femcontext.get_elem().neighbor_ptr(_femcontext.side) != nullptr))
459 232587 : continue;
460 :
461 : // Optionally initialize all the FE objects on this side.
462 15605 : if (_sys.fe_reinit_during_postprocess)
463 15605 : _femcontext.side_fe_reinit();
464 :
465 15605 : _sys.side_postprocess(_femcontext);
466 : }
467 : }
468 2048 : }
469 :
470 : private:
471 :
472 : FEMSystem & _sys;
473 : };
474 :
475 : class QoIContributions
476 : {
477 : public:
478 : /**
479 : * constructor to set context
480 : */
481 : explicit
482 39155 : QoIContributions(FEMSystem & sys,
483 : DifferentiableQoI & diff_qoi,
484 39155 : const QoISet & qoi_indices) :
485 40273 : qoi(sys.n_qois(), 0.), _sys(sys), _diff_qoi(diff_qoi),_qoi_indices(qoi_indices) {}
486 :
487 : /**
488 : * splitting constructor
489 : */
490 2236 : QoIContributions(const QoIContributions & other,
491 2236 : Threads::split) :
492 2236 : qoi(other._sys.n_qois(), 0.), _sys(other._sys), _diff_qoi(other._diff_qoi) {}
493 :
494 : /**
495 : * operator() for use with Threads::parallel_reduce().
496 : */
497 42509 : void operator()(const ConstElemRange & range)
498 : {
499 44745 : std::unique_ptr<DiffContext> con = _sys.build_context();
500 2236 : FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
501 42509 : _diff_qoi.init_context(_femcontext);
502 :
503 2236 : bool have_some_heterogenous_qoi_bc = false;
504 : #ifdef LIBMESH_ENABLE_CONSTRAINTS
505 46981 : std::vector<bool> have_heterogenous_qoi_bc(_sys.n_qois(), false);
506 103258 : for (auto q : make_range(_sys.n_qois()))
507 63945 : if (_qoi_indices.has_index(q) &&
508 6392 : _sys.get_dof_map().has_heterogenous_adjoint_constraints(q))
509 : {
510 0 : have_heterogenous_qoi_bc[q] = true;
511 0 : have_some_heterogenous_qoi_bc = true;
512 : }
513 : #endif
514 :
515 42509 : if (have_some_heterogenous_qoi_bc)
516 0 : _sys.init_context(_femcontext);
517 :
518 13461113 : for (const auto & elem : range)
519 : {
520 13418604 : _femcontext.pre_fe_reinit(_sys, elem);
521 :
522 : // We might have some heterogenous dofs here; let's see for
523 : // certain
524 1216614 : bool elem_has_some_heterogenous_qoi_bc = false;
525 :
526 : #ifdef LIBMESH_ENABLE_CONSTRAINTS
527 : const unsigned int n_dofs =
528 2433228 : cast_int<unsigned int>(_femcontext.get_dof_indices().size());
529 :
530 15851832 : std::vector<bool> elem_has_heterogenous_qoi_bc(_sys.n_qois(), false);
531 13418604 : if (have_some_heterogenous_qoi_bc)
532 : {
533 0 : for (auto q : make_range(_sys.n_qois()))
534 : {
535 0 : if (have_heterogenous_qoi_bc[q])
536 : {
537 0 : for (auto d : make_range(n_dofs))
538 0 : if (_sys.get_dof_map().has_heterogenous_adjoint_constraint
539 0 : (q, _femcontext.get_dof_indices()[d]) != Number(0))
540 : {
541 0 : elem_has_some_heterogenous_qoi_bc = true;
542 0 : elem_has_heterogenous_qoi_bc[q] = true;
543 0 : break;
544 : }
545 : }
546 : }
547 : }
548 : #endif
549 :
550 13418604 : if (_diff_qoi.assemble_qoi_elements ||
551 : elem_has_some_heterogenous_qoi_bc)
552 13418604 : _femcontext.elem_fe_reinit();
553 :
554 13418604 : if (_diff_qoi.assemble_qoi_elements)
555 13418604 : _diff_qoi.element_qoi(_femcontext, _qoi_indices);
556 :
557 : // If we have some heterogenous dofs here, those are
558 : // themselves part of a regularized flux QoI which the library
559 : // handles integrating
560 : #ifdef LIBMESH_ENABLE_CONSTRAINTS
561 13418604 : if (elem_has_some_heterogenous_qoi_bc)
562 : {
563 0 : _sys.time_solver->element_residual(false, _femcontext);
564 :
565 0 : for (auto q : make_range(_sys.n_qois()))
566 : {
567 0 : if (elem_has_heterogenous_qoi_bc[q])
568 : {
569 0 : for (auto d : make_range(n_dofs))
570 0 : this->qoi[q] -= _femcontext.get_elem_residual()(d) *
571 0 : _sys.get_dof_map().has_heterogenous_adjoint_constraint(q, _femcontext.get_dof_indices()[d]);
572 :
573 : }
574 : }
575 : }
576 : #endif
577 :
578 13418604 : const unsigned char n_sides = _femcontext.get_elem().n_sides();
579 67093020 : for (_femcontext.side = 0; _femcontext.side != n_sides;
580 53674416 : ++_femcontext.side)
581 : {
582 : // Don't compute on non-boundary sides unless requested
583 53675400 : if (!_diff_qoi.assemble_qoi_sides ||
584 11184 : (!_diff_qoi.assemble_qoi_internal_sides &&
585 11184 : _femcontext.get_elem().neighbor_ptr(_femcontext.side) != nullptr))
586 48805234 : continue;
587 :
588 2999 : _femcontext.side_fe_reinit();
589 :
590 2999 : _diff_qoi.side_qoi(_femcontext, _qoi_indices);
591 : }
592 : }
593 :
594 44745 : this->_diff_qoi.thread_join( this->qoi, _femcontext.get_qois(), _qoi_indices );
595 42509 : }
596 :
597 2236 : void join (const QoIContributions & other)
598 : {
599 1118 : libmesh_assert_equal_to (this->qoi.size(), other.qoi.size());
600 3354 : this->_diff_qoi.thread_join( this->qoi, other.qoi, _qoi_indices );
601 2236 : }
602 :
603 : std::vector<Number> qoi;
604 :
605 : private:
606 :
607 : FEMSystem & _sys;
608 : DifferentiableQoI & _diff_qoi;
609 :
610 : const QoISet _qoi_indices;
611 : };
612 :
613 : class QoIDerivativeContributions
614 : {
615 : public:
616 : /**
617 : * constructor to set context
618 : */
619 464 : QoIDerivativeContributions(FEMSystem & sys,
620 : const QoISet & qoi_indices,
621 : DifferentiableQoI & qoi,
622 : bool include_liftfunc,
623 14654 : bool apply_constraints) :
624 13726 : _sys(sys),
625 13726 : _qoi_indices(qoi_indices),
626 13726 : _qoi(qoi),
627 13726 : _include_liftfunc(include_liftfunc),
628 14654 : _apply_constraints(apply_constraints) {}
629 :
630 : /**
631 : * operator() for use with Threads::parallel_for().
632 : */
633 16016 : void operator()(const ConstElemRange & range) const
634 : {
635 16944 : std::unique_ptr<DiffContext> con = _sys.build_context();
636 928 : FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
637 16016 : _qoi.init_context(_femcontext);
638 :
639 928 : bool have_some_heterogenous_qoi_bc = false;
640 : #ifdef LIBMESH_ENABLE_CONSTRAINTS
641 17872 : std::vector<bool> have_heterogenous_qoi_bc(_sys.n_qois(), false);
642 16016 : if (_include_liftfunc || _apply_constraints)
643 43173 : for (auto q : make_range(_sys.n_qois()))
644 28757 : if (_qoi_indices.has_index(q) &&
645 3200 : _sys.get_dof_map().has_heterogenous_adjoint_constraints(q))
646 : {
647 48 : have_heterogenous_qoi_bc[q] = true;
648 24 : have_some_heterogenous_qoi_bc = true;
649 : }
650 : #endif
651 :
652 16016 : if (have_some_heterogenous_qoi_bc)
653 212 : _sys.init_context(_femcontext);
654 :
655 2432157 : for (const auto & elem : range)
656 : {
657 2416141 : _femcontext.pre_fe_reinit(_sys, elem);
658 :
659 : // We might have some heterogenous dofs here; let's see for
660 : // certain
661 219086 : bool elem_has_some_heterogenous_qoi_bc = false;
662 :
663 : #ifdef LIBMESH_ENABLE_CONSTRAINTS
664 : const unsigned int n_dofs =
665 438166 : cast_int<unsigned int>(_femcontext.get_dof_indices().size());
666 :
667 2854307 : std::vector<bool> elem_has_heterogenous_qoi_bc(_sys.n_qois(), false);
668 2416141 : if (have_some_heterogenous_qoi_bc)
669 : {
670 85476 : for (auto q : make_range(_sys.n_qois()))
671 : {
672 48450 : if (have_heterogenous_qoi_bc[q])
673 : {
674 412332 : for (auto d : make_range(n_dofs))
675 320980 : if (_sys.get_dof_map().has_heterogenous_adjoint_constraint
676 421568 : (q, _femcontext.get_dof_indices()[d]) != Number(0))
677 : {
678 140 : elem_has_some_heterogenous_qoi_bc = true;
679 280 : elem_has_heterogenous_qoi_bc[q] = true;
680 1680 : break;
681 : }
682 : }
683 : }
684 : }
685 : #endif
686 :
687 : // If we're going to call a user integral, then we need FE
688 : // information to call element_qoi.
689 : // If we're going to evaluate lift-function-based components
690 : // of a QoI, then we need FE information to assemble the
691 : // element residual.
692 2416141 : if (_qoi.assemble_qoi_elements ||
693 0 : ((_include_liftfunc || _apply_constraints) &&
694 : elem_has_some_heterogenous_qoi_bc))
695 2416141 : _femcontext.elem_fe_reinit();
696 :
697 2416141 : if (_qoi.assemble_qoi_elements)
698 2416141 : _qoi.element_qoi_derivative(_femcontext, _qoi_indices);
699 :
700 : #ifdef LIBMESH_ENABLE_CONSTRAINTS
701 : // If we need to use heterogenous dofs here, we need the
702 : // Jacobian either for the regularized flux QoI integration
703 : // and/or for constraint application.
704 2416141 : if ((_include_liftfunc || _apply_constraints) &&
705 : elem_has_some_heterogenous_qoi_bc)
706 : {
707 1820 : bool jacobian_computed = _sys.time_solver->element_residual(true, _femcontext);
708 :
709 : // If we're using numerical jacobians, above wont compute them
710 1680 : if (!jacobian_computed)
711 : {
712 : // Make sure we didn't compute a jacobian and lie about it
713 140 : libmesh_assert_equal_to (_femcontext.get_elem_jacobian().l1_norm(), 0.0);
714 : // Logging of numerical jacobians is done separately
715 1680 : _sys.numerical_elem_jacobian(_femcontext);
716 : }
717 : }
718 :
719 : // If we have some heterogenous dofs here, those are
720 : // themselves part of a regularized flux QoI which the library
721 : // may handle integrating
722 2416141 : if (_include_liftfunc && elem_has_some_heterogenous_qoi_bc)
723 : {
724 0 : for (auto q : make_range(_sys.n_qois()))
725 : {
726 0 : if (elem_has_heterogenous_qoi_bc[q])
727 : {
728 0 : for (auto i : make_range(n_dofs))
729 : {
730 : Number liftfunc_val =
731 0 : _sys.get_dof_map().has_heterogenous_adjoint_constraint(q, _femcontext.get_dof_indices()[i]);
732 :
733 0 : if (liftfunc_val != Number(0))
734 : {
735 0 : for (auto j : make_range(n_dofs))
736 0 : _femcontext.get_qoi_derivatives()[q](j) -=
737 0 : _femcontext.get_elem_jacobian()(i,j) *
738 : liftfunc_val;
739 : }
740 : }
741 : }
742 : }
743 : }
744 : #endif
745 :
746 :
747 2416141 : const unsigned char n_sides = _femcontext.get_elem().n_sides();
748 12080705 : for (_femcontext.side = 0; _femcontext.side != n_sides;
749 9664564 : ++_femcontext.side)
750 : {
751 : // Don't compute on non-boundary sides unless requested
752 9723948 : if (!_qoi.assemble_qoi_sides ||
753 649396 : (!_qoi.assemble_qoi_internal_sides &&
754 649396 : _femcontext.get_elem().neighbor_ptr(_femcontext.side) != nullptr))
755 8760650 : continue;
756 :
757 30947 : _femcontext.side_fe_reinit();
758 :
759 30947 : _qoi.side_qoi_derivative(_femcontext, _qoi_indices);
760 : }
761 :
762 : // We need some unmodified indices to use for constraining
763 : // multiple vector
764 : // FIXME - there should be a DofMap::constrain_element_vectors
765 : // to do this more efficiently
766 : #ifdef LIBMESH_ENABLE_CONSTRAINTS
767 2635227 : std::vector<dof_id_type> original_dofs = _femcontext.get_dof_indices();
768 : #endif
769 :
770 : { // A lock is necessary around access to the global system
771 438172 : femsystem_mutex::scoped_lock lock(assembly_mutex);
772 :
773 : #ifdef LIBMESH_ENABLE_CONSTRAINTS
774 : // We'll need to see if any heterogenous constraints apply
775 : // to the QoI dofs on this element *or* to any of the dofs
776 : // they depend on, so let's get those dependencies
777 2416141 : if (_apply_constraints)
778 2595285 : _sys.get_dof_map().constrain_nothing(_femcontext.get_dof_indices());
779 : #endif
780 :
781 4929446 : for (auto i : make_range(_sys.n_qois()))
782 2513305 : if (_qoi_indices.has_index(i))
783 : {
784 : #ifdef LIBMESH_ENABLE_CONSTRAINTS
785 2513305 : if (_apply_constraints)
786 : {
787 : #ifndef NDEBUG
788 225458 : bool has_heterogenous_constraint = false;
789 1269226 : for (auto d : make_range(n_dofs))
790 1043908 : if (_sys.get_dof_map().has_heterogenous_adjoint_constraint
791 1043908 : (i, _femcontext.get_dof_indices()[d]) != Number(0))
792 : {
793 140 : has_heterogenous_constraint = true;
794 140 : libmesh_assert(elem_has_heterogenous_qoi_bc[i]);
795 140 : libmesh_assert(elem_has_some_heterogenous_qoi_bc);
796 140 : break;
797 : }
798 : #else
799 : bool has_heterogenous_constraint =
800 225446 : elem_has_heterogenous_qoi_bc[i];
801 : #endif
802 :
803 2476441 : _femcontext.get_dof_indices() = original_dofs;
804 :
805 2476441 : if (has_heterogenous_constraint)
806 : {
807 : // Q_u gets used for *adjoint* solves, so we
808 : // need K^T here.
809 1960 : DenseMatrix<Number> elem_jacobian_transpose;
810 140 : _femcontext.get_elem_jacobian().get_transpose
811 1680 : (elem_jacobian_transpose);
812 :
813 1680 : _sys.get_dof_map().heterogenously_constrain_element_vector
814 1820 : (elem_jacobian_transpose,
815 280 : _femcontext.get_qoi_derivatives()[i],
816 : _femcontext.get_dof_indices(), false, i);
817 1400 : }
818 : else
819 : {
820 2474761 : _sys.get_dof_map().constrain_element_vector
821 2700067 : (_femcontext.get_qoi_derivatives()[i],
822 : _femcontext.get_dof_indices(), false);
823 : }
824 : }
825 : #endif
826 :
827 2513305 : _sys.get_adjoint_rhs(i).add_vector
828 2513305 : (_femcontext.get_qoi_derivatives()[i], _femcontext.get_dof_indices());
829 : }
830 : }
831 : }
832 16016 : }
833 :
834 : private:
835 :
836 : FEMSystem & _sys;
837 : const QoISet & _qoi_indices;
838 : DifferentiableQoI & _qoi;
839 : bool _include_liftfunc, _apply_constraints;
840 : };
841 :
842 :
843 : }
844 :
845 :
846 : namespace libMesh
847 : {
848 :
849 :
850 :
851 :
852 :
853 6647 : FEMSystem::FEMSystem (EquationSystems & es,
854 : const std::string & name_in,
855 0 : const unsigned int number_in)
856 : : Parent(es, name_in, number_in),
857 6279 : fe_reinit_during_postprocess(true),
858 6279 : numerical_jacobian_h(TOLERANCE),
859 6647 : verify_analytic_jacobians(0.0)
860 : {
861 6647 : }
862 :
863 :
864 6279 : FEMSystem::~FEMSystem () = default;
865 :
866 :
867 :
868 6647 : void FEMSystem::init_data ()
869 : {
870 : // First initialize LinearImplicitSystem data
871 6647 : Parent::init_data();
872 6647 : }
873 :
874 :
875 218854 : void FEMSystem::assembly (bool get_residual, bool get_jacobian,
876 : bool apply_heterogeneous_constraints,
877 : bool apply_no_constraints)
878 : {
879 6268 : libmesh_assert(get_residual || get_jacobian);
880 :
881 : // Log residual and jacobian and combined performance separately
882 : #ifdef LIBMESH_ENABLE_PERFORMANCE_LOGGING
883 : const char * log_name;
884 12536 : if (get_residual && get_jacobian)
885 1374 : log_name = "assembly()";
886 9788 : else if (get_residual)
887 4242 : log_name = "assembly(get_residual)";
888 : else
889 652 : log_name = "assembly(get_jacobian)";
890 :
891 12536 : LOG_SCOPE(log_name, "FEMSystem");
892 : #endif
893 :
894 12536 : const MeshBase & mesh = this->get_mesh();
895 :
896 218854 : if (print_solution_norms)
897 : {
898 0 : this->solution->close();
899 :
900 0 : std::streamsize old_precision = libMesh::out.precision();
901 0 : libMesh::out.precision(16);
902 0 : libMesh::out << "|U| = "
903 0 : << this->solution->l1_norm()
904 0 : << std::endl;
905 0 : libMesh::out.precision(old_precision);
906 : }
907 218854 : if (print_solutions)
908 : {
909 0 : std::streamsize old_precision = libMesh::out.precision();
910 0 : libMesh::out.precision(16);
911 0 : libMesh::out << "U = [" << *(this->solution)
912 0 : << "];" << std::endl;
913 0 : libMesh::out.precision(old_precision);
914 : }
915 :
916 : // Is this definitely necessary? [RHS]
917 : // Yes. [RHS 2012]
918 218854 : if (get_jacobian)
919 69277 : matrix->zero();
920 218854 : if (get_residual)
921 197530 : rhs->zero();
922 :
923 : // Stupid C++ lets you set *Real* verify_analytic_jacobians = true!
924 218854 : if (verify_analytic_jacobians > 0.5)
925 : {
926 0 : libMesh::err << "WARNING! verify_analytic_jacobians was set "
927 0 : << "to absurdly large value of "
928 0 : << verify_analytic_jacobians << std::endl;
929 0 : libMesh::err << "Resetting to 1e-6!" << std::endl;
930 0 : verify_analytic_jacobians = 1e-6;
931 : }
932 :
933 : // In time-dependent problems, the nonlinear function we're trying
934 : // to solve at each timestep may depend on the particular solver
935 : // we're using
936 6268 : libmesh_assert(time_solver.get());
937 :
938 : // Build the residual and jacobian contributions on every active
939 : // mesh element on this processor
940 : Threads::parallel_for
941 225122 : (mesh.active_local_element_stored_range(),
942 218854 : AssemblyContributions(*this, get_residual, get_jacobian,
943 : apply_heterogeneous_constraints,
944 : apply_no_constraints));
945 :
946 : // Check and see if we have SCALAR variables
947 6268 : bool have_scalar = false;
948 481238 : for (auto i : make_range(this->n_variable_groups()))
949 : {
950 262384 : if (this->variable_group(i).type().family == SCALAR)
951 : {
952 0 : have_scalar = true;
953 0 : break;
954 : }
955 : }
956 :
957 : // SCALAR dofs are stored on the last processor, so we'll evaluate
958 : // their equation terms there and only if we have a SCALAR variable
959 225122 : if (this->processor_id() == (this->n_processors()-1) && have_scalar)
960 : {
961 0 : std::unique_ptr<DiffContext> con = this->build_context();
962 0 : FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
963 0 : this->init_context(_femcontext);
964 0 : _femcontext.pre_fe_reinit(*this, nullptr);
965 :
966 : bool jacobian_computed =
967 0 : this->time_solver->nonlocal_residual(get_jacobian, _femcontext);
968 :
969 : // Nonlocal residuals are likely to be length 0, in which case we
970 : // don't need to do any more. And we shouldn't try to do any
971 : // more; lots of DenseVector/DenseMatrix code assumes rank>0.
972 0 : if (_femcontext.get_elem_residual().size())
973 : {
974 : // Compute a numeric jacobian if we have to
975 0 : if (get_jacobian && !jacobian_computed)
976 : {
977 : // Make sure we didn't compute a jacobian and lie about it
978 0 : libmesh_assert_equal_to (_femcontext.get_elem_jacobian().l1_norm(), 0.0);
979 : // Logging of numerical jacobians is done separately
980 0 : this->numerical_nonlocal_jacobian(_femcontext);
981 : }
982 :
983 : // Compute a numeric jacobian if we're asked to verify the
984 : // analytic jacobian we got
985 0 : if (get_jacobian && jacobian_computed &&
986 0 : this->verify_analytic_jacobians != 0.0)
987 : {
988 0 : DenseMatrix<Number> analytic_jacobian(_femcontext.get_elem_jacobian());
989 :
990 0 : _femcontext.get_elem_jacobian().zero();
991 : // Logging of numerical jacobians is done separately
992 0 : this->numerical_nonlocal_jacobian(_femcontext);
993 :
994 0 : Real analytic_norm = analytic_jacobian.l1_norm();
995 0 : Real numerical_norm = _femcontext.get_elem_jacobian().l1_norm();
996 :
997 : // If we can continue, we'll probably prefer the analytic jacobian
998 0 : analytic_jacobian.swap(_femcontext.get_elem_jacobian());
999 :
1000 : // The matrix "analytic_jacobian" will now hold the error matrix
1001 0 : analytic_jacobian.add(-1.0, _femcontext.get_elem_jacobian());
1002 0 : Real error_norm = analytic_jacobian.l1_norm();
1003 :
1004 0 : Real relative_error = error_norm /
1005 0 : std::max(analytic_norm, numerical_norm);
1006 :
1007 0 : if (relative_error > this->verify_analytic_jacobians)
1008 : {
1009 0 : libMesh::err << "Relative error " << relative_error
1010 0 : << " detected in analytic jacobian on nonlocal dofs!"
1011 0 : << std::endl;
1012 :
1013 0 : std::streamsize old_precision = libMesh::out.precision();
1014 0 : libMesh::out.precision(16);
1015 0 : libMesh::out << "J_analytic nonlocal = "
1016 0 : << _femcontext.get_elem_jacobian() << std::endl;
1017 0 : analytic_jacobian.add(1.0, _femcontext.get_elem_jacobian());
1018 0 : libMesh::out << "J_numeric nonlocal = "
1019 0 : << analytic_jacobian << std::endl;
1020 :
1021 0 : libMesh::out.precision(old_precision);
1022 :
1023 0 : libmesh_error_msg("Relative error too large, exiting!");
1024 : }
1025 0 : }
1026 :
1027 : add_element_system
1028 0 : (*this, get_residual, get_jacobian,
1029 : apply_heterogeneous_constraints, apply_no_constraints, _femcontext);
1030 : }
1031 0 : }
1032 :
1033 218854 : if (get_residual && (print_residual_norms || print_residuals))
1034 0 : this->rhs->close();
1035 198834 : if (get_residual && print_residual_norms)
1036 : {
1037 0 : std::streamsize old_precision = libMesh::out.precision();
1038 0 : libMesh::out.precision(16);
1039 0 : libMesh::out << "|F| = " << this->rhs->l1_norm() << std::endl;
1040 0 : libMesh::out.precision(old_precision);
1041 : }
1042 198834 : if (get_residual && print_residuals)
1043 : {
1044 0 : std::streamsize old_precision = libMesh::out.precision();
1045 0 : libMesh::out.precision(16);
1046 0 : libMesh::out << "F = [" << *(this->rhs) << "];" << std::endl;
1047 0 : libMesh::out.precision(old_precision);
1048 : }
1049 :
1050 218854 : if (get_jacobian && (print_jacobian_norms || print_jacobians))
1051 0 : this->matrix->close();
1052 77761 : if (get_jacobian && print_jacobian_norms)
1053 : {
1054 0 : std::streamsize old_precision = libMesh::out.precision();
1055 0 : libMesh::out.precision(16);
1056 0 : libMesh::out << "|J| = " << this->matrix->l1_norm() << std::endl;
1057 0 : libMesh::out.precision(old_precision);
1058 : }
1059 77761 : if (get_jacobian && print_jacobians)
1060 : {
1061 0 : std::streamsize old_precision = libMesh::out.precision();
1062 0 : libMesh::out.precision(16);
1063 0 : libMesh::out << "J = [" << *(this->matrix) << "];" << std::endl;
1064 0 : libMesh::out.precision(old_precision);
1065 : }
1066 218854 : }
1067 :
1068 :
1069 :
1070 26782 : void FEMSystem::solve()
1071 : {
1072 : // We are solving the primal problem
1073 26782 : Parent::solve();
1074 :
1075 : // On a moving mesh we want the mesh to reflect the new solution
1076 26782 : this->mesh_position_set();
1077 26782 : }
1078 :
1079 :
1080 :
1081 29057 : void FEMSystem::mesh_position_set()
1082 : {
1083 : // If we don't need to move the mesh, we're done
1084 29057 : if (_mesh_sys != this)
1085 26457 : return;
1086 :
1087 0 : MeshBase & mesh = this->get_mesh();
1088 :
1089 2600 : std::unique_ptr<DiffContext> con = this->build_context();
1090 0 : FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
1091 2600 : this->init_context(_femcontext);
1092 :
1093 : // Move every mesh element we can
1094 51280 : for (const auto & elem : mesh.active_local_element_ptr_range())
1095 : {
1096 : // We need the algebraic data
1097 23040 : _femcontext.pre_fe_reinit(*this, elem);
1098 : // And when asserts are on, we also need the FE so
1099 : // we can assert that the mesh data is of the right type.
1100 : #ifndef NDEBUG
1101 0 : _femcontext.elem_fe_reinit();
1102 : #endif
1103 :
1104 : // This code won't handle moving subactive elements
1105 0 : libmesh_assert(!_femcontext.get_elem().has_children());
1106 :
1107 0 : _femcontext.elem_position_set(1.);
1108 2600 : }
1109 :
1110 : // We've now got positions set on all local nodes (and some
1111 : // semilocal nodes); let's request positions for non-local nodes
1112 : // from their processors.
1113 :
1114 2600 : SyncNodalPositions sync_object(mesh);
1115 : Parallel::sync_dofobject_data_by_id
1116 5200 : (this->comm(), mesh.nodes_begin(), mesh.nodes_end(), sync_object);
1117 2600 : }
1118 :
1119 :
1120 :
1121 1976 : void FEMSystem::postprocess ()
1122 : {
1123 102 : LOG_SCOPE("postprocess()", "FEMSystem");
1124 :
1125 204 : const MeshBase & mesh = this->get_mesh();
1126 :
1127 1976 : this->update();
1128 :
1129 : // Get the time solver object associated with the system, and tell it that
1130 : // we are not solving the adjoint problem
1131 102 : this->get_time_solver().set_is_adjoint(false);
1132 :
1133 : // Loop over every active mesh element on this processor
1134 2078 : Threads::parallel_for (mesh.active_local_element_stored_range(),
1135 2078 : PostprocessContributions(*this));
1136 1976 : }
1137 :
1138 :
1139 :
1140 39155 : void FEMSystem::assemble_qoi (const QoISet & qoi_indices)
1141 : {
1142 2236 : LOG_SCOPE("assemble_qoi()", "FEMSystem");
1143 :
1144 2236 : const MeshBase & mesh = this->get_mesh();
1145 :
1146 39155 : this->update();
1147 :
1148 1118 : const unsigned int Nq = this->n_qois();
1149 :
1150 : // the quantity of interest is assumed to be a sum of element and
1151 : // side terms
1152 95110 : for (unsigned int i=0; i != Nq; ++i)
1153 55955 : if (qoi_indices.has_index(i))
1154 55955 : this->set_qoi(i, 0);
1155 :
1156 : // Create a non-temporary qoi_contributions object, so we can query
1157 : // its results after the reduction
1158 79428 : QoIContributions qoi_contributions(*this, *(this->get_qoi()), qoi_indices);
1159 :
1160 : // Loop over every active mesh element on this processor
1161 39155 : Threads::parallel_reduce(mesh.active_local_element_stored_range(),
1162 : qoi_contributions);
1163 :
1164 39155 : std::vector<Number> global_qoi = this->get_qoi_values();
1165 39155 : this->get_qoi()->parallel_op( this->comm(), global_qoi, qoi_contributions.qoi, qoi_indices );
1166 77192 : this->set_qoi(std::move(global_qoi));
1167 39155 : }
1168 :
1169 :
1170 :
1171 14654 : void FEMSystem::assemble_qoi_derivative (const QoISet & qoi_indices,
1172 : bool include_liftfunc,
1173 : bool apply_constraints)
1174 : {
1175 928 : LOG_SCOPE("assemble_qoi_derivative()", "FEMSystem");
1176 :
1177 928 : const MeshBase & mesh = this->get_mesh();
1178 :
1179 14654 : this->update();
1180 :
1181 : // The quantity of interest derivative assembly accumulates on
1182 : // initially zero vectors
1183 39471 : for (auto i : make_range(this->n_qois()))
1184 24817 : if (qoi_indices.has_index(i))
1185 24817 : this->add_adjoint_rhs(i).zero();
1186 :
1187 : // Loop over every active mesh element on this processor
1188 15118 : Threads::parallel_for (mesh.active_local_element_stored_range(),
1189 14190 : QoIDerivativeContributions(*this, qoi_indices,
1190 14654 : *(this->get_qoi()),
1191 : include_liftfunc,
1192 : apply_constraints));
1193 :
1194 39471 : for (auto i : make_range(this->n_qois()))
1195 24817 : if (qoi_indices.has_index(i))
1196 24817 : this->get_qoi()->finalize_derivative(this->get_adjoint_rhs(i),i);
1197 14654 : }
1198 :
1199 :
1200 :
1201 278124 : void FEMSystem::numerical_jacobian (TimeSolverResPtr res,
1202 : FEMContext & context) const
1203 : {
1204 : // Logging is done by numerical_elem_jacobian
1205 : // or numerical_side_jacobian
1206 :
1207 54620 : DenseVector<Number> original_residual(context.get_elem_residual());
1208 54620 : DenseVector<Number> backwards_residual(context.get_elem_residual());
1209 332728 : DenseMatrix<Number> numeric_jacobian(context.get_elem_jacobian());
1210 : #ifdef DEBUG
1211 54620 : DenseMatrix<Number> old_jacobian(context.get_elem_jacobian());
1212 : #endif
1213 :
1214 27310 : Real numerical_point_h = 0.;
1215 278124 : if (_mesh_sys == this)
1216 0 : numerical_point_h = numerical_jacobian_h * context.get_elem().hmin();
1217 :
1218 : const unsigned int n_dofs =
1219 54604 : cast_int<unsigned int>(context.get_dof_indices().size());
1220 :
1221 608520 : for (auto v : make_range(context.n_vars()))
1222 : {
1223 298746 : const Real my_h = this->numerical_jacobian_h_for_var(v);
1224 :
1225 31666 : unsigned int j_offset = libMesh::invalid_uint;
1226 :
1227 330396 : if (!context.get_dof_indices(v).empty())
1228 : {
1229 4320120 : for (auto i : make_range(n_dofs))
1230 4728928 : if (context.get_dof_indices()[i] ==
1231 369674 : context.get_dof_indices(v)[0])
1232 31666 : j_offset = i;
1233 :
1234 31666 : libmesh_assert_not_equal_to(j_offset, libMesh::invalid_uint);
1235 : }
1236 :
1237 3174168 : for (auto j : make_range(context.get_dof_indices(v).size()))
1238 : {
1239 2843772 : const unsigned int total_j = j + j_offset;
1240 :
1241 : // Take the "minus" side of a central differenced first derivative
1242 2843772 : Number original_solution = context.get_elem_solution(v)(j);
1243 2597710 : context.get_elem_solution(v)(j) -= my_h;
1244 :
1245 : // Make sure to catch any moving mesh terms
1246 274178 : Real * coord = nullptr;
1247 2843772 : if (_mesh_sys == this)
1248 : {
1249 0 : if (_mesh_x_var == v)
1250 0 : coord = &(context.get_elem().point(j)(0));
1251 0 : else if (_mesh_y_var == v)
1252 0 : coord = &(context.get_elem().point(j)(1));
1253 0 : else if (_mesh_z_var == v)
1254 0 : coord = &(context.get_elem().point(j)(2));
1255 : }
1256 274178 : if (coord)
1257 : {
1258 : // We have enough information to scale the perturbations
1259 : // here appropriately
1260 0 : context.get_elem_solution(v)(j) = original_solution - numerical_point_h;
1261 0 : *coord = libmesh_real(context.get_elem_solution(v)(j));
1262 : }
1263 :
1264 274178 : context.get_elem_residual().zero();
1265 2843772 : ((*time_solver).*(res))(false, context);
1266 : #ifdef DEBUG
1267 274178 : libmesh_assert_equal_to (old_jacobian, context.get_elem_jacobian());
1268 : #endif
1269 274178 : backwards_residual = context.get_elem_residual();
1270 :
1271 : // Take the "plus" side of a central differenced first derivative
1272 2843772 : context.get_elem_solution(v)(j) = original_solution + my_h;
1273 2843772 : if (coord)
1274 : {
1275 0 : context.get_elem_solution()(j) = original_solution + numerical_point_h;
1276 0 : *coord = libmesh_real(context.get_elem_solution(v)(j));
1277 : }
1278 274178 : context.get_elem_residual().zero();
1279 2843772 : ((*time_solver).*(res))(false, context);
1280 : #ifdef DEBUG
1281 274178 : libmesh_assert_equal_to (old_jacobian, context.get_elem_jacobian());
1282 : #endif
1283 :
1284 2843772 : context.get_elem_solution(v)(j) = original_solution;
1285 2843772 : if (coord)
1286 : {
1287 0 : *coord = libmesh_real(context.get_elem_solution(v)(j));
1288 0 : for (auto i : make_range(n_dofs))
1289 : {
1290 0 : numeric_jacobian(i,total_j) =
1291 0 : (context.get_elem_residual()(i) - backwards_residual(i)) /
1292 0 : 2. / numerical_point_h;
1293 : }
1294 : }
1295 : else
1296 : {
1297 37022520 : for (auto i : make_range(n_dofs))
1298 : {
1299 34178748 : numeric_jacobian(i,total_j) =
1300 31248790 : (context.get_elem_residual()(i) - backwards_residual(i)) /
1301 31248790 : 2. / my_h;
1302 : }
1303 : }
1304 : }
1305 : }
1306 :
1307 27310 : context.get_elem_residual() = original_residual;
1308 278124 : context.get_elem_jacobian() = numeric_jacobian;
1309 501644 : }
1310 :
1311 :
1312 :
1313 228984 : void FEMSystem::numerical_elem_jacobian (FEMContext & context) const
1314 : {
1315 43392 : LOG_SCOPE("numerical_elem_jacobian()", "FEMSystem");
1316 228984 : this->numerical_jacobian(&TimeSolver::element_residual, context);
1317 228984 : }
1318 :
1319 :
1320 :
1321 49140 : void FEMSystem::numerical_side_jacobian (FEMContext & context) const
1322 : {
1323 11228 : LOG_SCOPE("numerical_side_jacobian()", "FEMSystem");
1324 49140 : this->numerical_jacobian(&TimeSolver::side_residual, context);
1325 49140 : }
1326 :
1327 :
1328 :
1329 0 : void FEMSystem::numerical_nonlocal_jacobian (FEMContext & context) const
1330 : {
1331 0 : LOG_SCOPE("numerical_nonlocal_jacobian()", "FEMSystem");
1332 0 : this->numerical_jacobian(&TimeSolver::nonlocal_residual, context);
1333 0 : }
1334 :
1335 :
1336 :
1337 334272 : std::unique_ptr<DiffContext> FEMSystem::build_context ()
1338 : {
1339 350316 : auto fc = std::make_unique<FEMContext>(*this);
1340 :
1341 334272 : DifferentiablePhysics * phys = this->get_physics();
1342 :
1343 16044 : libmesh_assert (phys);
1344 :
1345 : // If we are solving a moving mesh problem, tell that to the Context
1346 334272 : fc->set_mesh_system(phys->get_mesh_system());
1347 32088 : fc->set_mesh_x_var(phys->get_mesh_x_var());
1348 32088 : fc->set_mesh_y_var(phys->get_mesh_y_var());
1349 32088 : fc->set_mesh_z_var(phys->get_mesh_z_var());
1350 :
1351 334272 : fc->set_deltat_pointer( &deltat );
1352 :
1353 : // If we are solving the adjoint problem, tell that to the Context
1354 350316 : fc->is_adjoint() = this->get_time_solver().is_adjoint();
1355 :
1356 350316 : return fc;
1357 302184 : }
1358 :
1359 :
1360 :
1361 258451 : void FEMSystem::init_context(DiffContext & c)
1362 : {
1363 : // Parent::init_context(c); // may be a good idea in derived classes
1364 :
1365 : // Although we do this in DiffSystem::build_context() and
1366 : // FEMSystem::build_context() as well, we do it here just to be
1367 : // extra sure that the deltat pointer gets set. Since the
1368 : // intended behavior is for classes derived from FEMSystem to
1369 : // call Parent::init_context() in their own init_context()
1370 : // overloads, we can ensure that those classes get the correct
1371 : // deltat pointers even if they have different build_context()
1372 : // overloads.
1373 258451 : c.set_deltat_pointer ( &deltat );
1374 :
1375 12156 : FEMContext & context = cast_ref<FEMContext &>(c);
1376 :
1377 : // Make sure we're prepared to do mass integration
1378 621390 : for (auto var : make_range(this->n_vars()))
1379 709714 : if (this->get_physics()->is_time_evolving(var))
1380 : {
1381 : // Request shape functions based on FEType
1382 192107 : switch( FEInterface::field_type( this->variable_type(var) ) )
1383 : {
1384 192107 : case( TYPE_SCALAR ):
1385 : {
1386 9596 : FEBase * elem_fe = nullptr;
1387 385369 : for (auto dim : context.elem_dimensions())
1388 : {
1389 9656 : context.get_element_fe(var, elem_fe, dim);
1390 19638 : elem_fe->get_JxW();
1391 9656 : elem_fe->get_phi();
1392 0 : }
1393 : }
1394 9596 : break;
1395 0 : case( TYPE_VECTOR ):
1396 : {
1397 0 : FEGenericBase<RealGradient> * elem_fe = nullptr;
1398 0 : for (auto dim : context.elem_dimensions())
1399 : {
1400 0 : context.get_element_fe(var, elem_fe, dim);
1401 0 : elem_fe->get_JxW();
1402 0 : elem_fe->get_phi();
1403 0 : }
1404 : }
1405 0 : break;
1406 0 : default:
1407 0 : libmesh_error_msg("Unrecognized field type!");
1408 : }
1409 : }
1410 258451 : }
1411 :
1412 :
1413 :
1414 65 : void FEMSystem::mesh_position_get()
1415 : {
1416 : // This function makes no sense unless we've already picked out some
1417 : // variable(s) to reflect mesh position coordinates
1418 65 : libmesh_error_msg_if(!_mesh_sys, "_mesh_sys was nullptr!");
1419 :
1420 : // We currently assume mesh variables are in our own system
1421 65 : if (_mesh_sys != this)
1422 0 : libmesh_not_implemented();
1423 :
1424 : // Loop over every active mesh element on this processor
1425 0 : const MeshBase & mesh = this->get_mesh();
1426 :
1427 65 : std::unique_ptr<DiffContext> con = this->build_context();
1428 0 : FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
1429 65 : this->init_context(_femcontext);
1430 :
1431 : // Get the solution's mesh variables from every element
1432 1282 : for (const auto & elem : mesh.active_local_element_ptr_range())
1433 : {
1434 576 : _femcontext.pre_fe_reinit(*this, elem);
1435 :
1436 576 : _femcontext.elem_position_get();
1437 :
1438 576 : if (_mesh_x_var != libMesh::invalid_uint)
1439 576 : this->solution->insert(_femcontext.get_elem_solution(_mesh_x_var),
1440 0 : _femcontext.get_dof_indices(_mesh_x_var) );
1441 576 : if (_mesh_y_var != libMesh::invalid_uint)
1442 576 : this->solution->insert(_femcontext.get_elem_solution(_mesh_y_var),
1443 0 : _femcontext.get_dof_indices(_mesh_y_var));
1444 576 : if (_mesh_z_var != libMesh::invalid_uint)
1445 576 : this->solution->insert(_femcontext.get_elem_solution(_mesh_z_var),
1446 0 : _femcontext.get_dof_indices(_mesh_z_var));
1447 65 : }
1448 :
1449 65 : this->solution->close();
1450 :
1451 : // And make sure the current_local_solution is up to date too
1452 65 : this->System::update();
1453 65 : }
1454 :
1455 : } // namespace libMesh
|