/home/runner/work/DiFfRG_current/DiFfRG_current/DiFfRG/include/DiFfRG/discretization/FV/assembler/KurganovTadmor.hh Source File#

DiFfRG: /home/runner/work/DiFfRG_current/DiFfRG_current/DiFfRG/include/DiFfRG/discretization/FV/assembler/KurganovTadmor.hh Source File
DiFfRG
Discretization Framework for functional Renormalization Group flows
KurganovTadmor.hh
Go to the documentation of this file.
1#pragma once
2
3// external libraries
4
5// DiFfRG
7#include <array>
8#include <autodiff/forward/real/real.hpp>
9#include <cstddef>
10#include <deal.II/base/multithread_info.h>
11#include <deal.II/base/point.h>
12#include <deal.II/base/quadrature_lib.h>
13#include <deal.II/base/timer.h>
14#include <deal.II/base/types.h>
15#include <deal.II/dofs/dof_handler.h>
16#include <deal.II/dofs/dof_tools.h>
17#include <deal.II/fe/fe_values.h>
18#include <deal.II/grid/grid_tools.h>
19#include <deal.II/grid/tria_iterator_base.h>
20#include <deal.II/lac/full_matrix.h>
21#include <deal.II/lac/sparse_matrix.h>
22#include <deal.II/lac/vector.h>
23#include <deal.II/meshworker/assemble_flags.h>
24#include <deal.II/meshworker/mesh_loop.h>
25#include <deal.II/numerics/matrix_tools.h>
26#include <deal.II/numerics/vector_tools.h>
27#include <iomanip>
28#include <iostream>
29#include <limits>
30#include <optional>
31#include <spdlog/spdlog.h>
32#include <sstream>
33#include <tbb/tbb.h>
34
45
54#include <tuple>
55#include <utility>
56#include <vector>
57
58namespace DiFfRG
59{
60 namespace FV
61 {
62 namespace KurganovTadmor
63 {
64 using namespace dealii;
65
66 namespace internal
67 {
72 template <int dim, typename NumberType, size_t n_components> struct ScratchData {
73 using QuadratureValue = std::array<NumberType, n_components>;
74
75 ScratchData(const dealii::Quadrature<dim> &quadrature)
76 : cell_dof_indices(n_components), ncell_dof_indices(n_components), solution_values(quadrature.size()),
77 solution_dot_values(quadrature.size())
78 {
79 }
80
82 : cell_dof_indices(n_components), ncell_dof_indices(n_components),
83 solution_values(scratch_data.solution_values.size()),
84 solution_dot_values(scratch_data.solution_dot_values.size())
85 {
86 }
87
88 std::vector<types::global_dof_index> cell_dof_indices;
89 std::vector<types::global_dof_index> ncell_dof_indices;
90 std::vector<QuadratureValue> solution_values;
91 std::vector<QuadratureValue> solution_dot_values;
92 std::array<std::vector<ReconstructionDerivativeData<dim, NumberType, n_components>>, 2>
94 std::array<std::vector<ReconstructionDerivativeData<dim, NumberType, n_components>>, 2> diffusion_derivatives;
95 // d(cell-centre gradient)/d(u_j) for the nonlocal part of the source jacobian. Separate from
96 // reconstructed_derivatives, which the face workers resize for their own dependency sets.
97 std::vector<GradientType<dim, NumberType, n_components>> source_gradient_derivatives;
101 };
102
103 // TODO fewer memory allocations
104 template <typename NumberType> struct CopyData_R {
106 Vector<NumberType> cell_residual;
107 std::vector<types::global_dof_index> joint_dof_indices;
108
109 void reinit(const unsigned int n_face_dofs)
110 {
111 cell_residual.reinit(n_face_dofs);
112 joint_dof_indices.resize(n_face_dofs);
113 }
114 };
115
116 Vector<NumberType> cell_residual;
117 Vector<NumberType> cell_mass;
118 std::vector<types::global_dof_index> local_dof_indices;
119 std::vector<CopyDataFace_R> face_data;
120 unsigned int active_face_count = 0;
121
122 template <class Iterator> void reinit(const Iterator &cell, uint dofs_per_cell)
123 {
124 cell_residual.reinit(dofs_per_cell);
125 cell_mass.reinit(dofs_per_cell);
126 local_dof_indices.resize(dofs_per_cell);
127 if (face_data.size() != cell->n_faces()) face_data.resize(cell->n_faces());
129 cell->get_dof_indices(local_dof_indices);
130 }
131
133 {
134 AssertIndexRange(active_face_count, face_data.size());
136 }
137 };
138
139 // TODO fewer memory allocations
140 template <typename NumberType, int dim> struct CopyData_J {
141 using Iterator = typename DoFHandler<dim>::active_cell_iterator;
142
144 // since face dofs do not only depend on themself but also on their neighbors (because of the
145 // reconstruction) we need to consider a general rectengular local jacobi matrix
146 FullMatrix<NumberType> cell_jacobian;
147 FullMatrix<NumberType> extractor_cell_jacobian;
148 std::vector<types::global_dof_index> to_dofs;
149 std::vector<types::global_dof_index> from_dofs;
150
151 void reinit(const std::vector<types::global_dof_index> &cached_to_dofs,
152 const std::vector<types::global_dof_index> &cached_from_dofs)
153 {
154 to_dofs = cached_to_dofs;
155 from_dofs = cached_from_dofs;
156 cell_jacobian.reinit(size(to_dofs), size(from_dofs));
157 }
158 };
159
160 FullMatrix<NumberType> cell_jacobian;
161 FullMatrix<NumberType> extractor_cell_jacobian;
162 FullMatrix<NumberType> cell_mass_jacobian;
163 std::vector<types::global_dof_index> local_dof_indices;
164 std::vector<CopyDataFace_J> face_data;
165 unsigned int active_face_count = 0;
166
167 template <class Iterator> void reinit(const Iterator &cell, uint dofs_per_cell, uint n_extractors)
168 {
169 cell_jacobian.reinit(dofs_per_cell, dofs_per_cell);
170 if (n_extractors > 0) extractor_cell_jacobian.reinit(dofs_per_cell, n_extractors);
171 cell_mass_jacobian.reinit(dofs_per_cell, dofs_per_cell);
172 local_dof_indices.resize(dofs_per_cell);
173 // +1: the cell worker claims one block for the nonlocal source jacobian before the faces do.
174 if (face_data.size() != cell->n_faces() + 1) face_data.resize(cell->n_faces() + 1);
176 cell->get_dof_indices(local_dof_indices);
177 }
178
180 {
181 AssertIndexRange(active_face_count, face_data.size());
183 }
184 };
185
186 template <typename NumberType> struct CopyData_I {
188 std::array<uint, 2> cell_indices{};
189 std::array<double, 2> values{};
190 };
191 std::vector<CopyFaceData_I> face_data;
192 double value = 0.;
194 };
195
196 template <typename T> int sgn(T val) { return (T{} < val) - (val < T{}); }
197
205 template <typename NumberType, int dim, size_t n_components>
206 std::array<JacobianMatrix<NumberType, n_components>, dim>
208 const std::array<int, n_components> &blocks, const int block)
209 {
210 std::array<JacobianMatrix<NumberType, n_components>, dim> restricted{};
211 for (size_t d = 0; d < dim; ++d)
212 for (size_t i = 0; i < n_components; ++i) {
213 if (blocks[i] != block) continue;
214 for (size_t j = 0; j < n_components; ++j)
215 if (blocks[j] == block) restricted[d][i][j] = J[d][i][j];
216 }
217 return restricted;
218 }
219
226 template <typename NumberType, int dim, size_t n_components>
229 const std::array<int, n_components> &blocks, const int block)
230 {
232 for (size_t d = 0; d < dim; ++d)
233 for (size_t i = 0; i < n_components; ++i) {
234 if (blocks[i] != block) continue;
235 for (size_t j = 0; j < n_components; ++j) {
236 if (blocks[j] != block) continue;
237 for (size_t c = 0; c < n_components; ++c)
238 restricted[d][i][j][c] = H[d][i][j][c];
239 }
240 }
241 return restricted;
242 }
243
251 template <size_t n_components, typename Result, typename ComputeFUN>
252 std::array<Result, n_components> per_block(const std::array<int, n_components> &blocks,
253 const ComputeFUN &compute)
254 {
255 std::array<Result, n_components> per_component{};
256 for (size_t i = 0; i < n_components; ++i) {
257 if (blocks[i] < 0) continue;
258 size_t source = i;
259 for (size_t j = 0; j < i; ++j)
260 if (blocks[j] == blocks[i]) {
261 source = j;
262 break;
263 }
264 per_component[i] = source == i ? compute(blocks[i]) : per_component[source];
265 }
266 return per_component;
267 }
268
272 template <int dim, typename NumberType, size_t n_components> struct KTFluxData {
273 std::array<dealii::Tensor<1, dim, NumberType>, n_components> F_plus;
274 std::array<dealii::Tensor<1, dim, NumberType>, n_components> F_minus;
275 std::array<dealii::Tensor<1, dim, NumberType>, n_components> a_half;
276 };
277
278 template <typename WaveSpeedStrategy, typename Model, typename NumberType, int dim, size_t n_components,
279 typename ExtractorArray, typename VariableVector>
281 const std::array<NumberType, n_components> &u_plus, const std::array<NumberType, n_components> &u_minus,
283 const GradientType<dim, NumberType, n_components> &grad_u_minus, const dealii::Point<dim> &x_q,
284 const double cell_width_plus, const double cell_width_minus, const ExtractorArray &extractors,
285 const VariableVector &variables, const Model &model)
286 {
287 using ADNumberType = autodiff::Real<1, NumberType>;
288
290
291 std::array<ADNumberType, n_components> u_plus_AD{}, u_minus_AD{};
292 GradientType<dim, ADNumberType, n_components> grad_u_plus_AD{}, grad_u_minus_AD{};
293 for (size_t i = 0; i < n_components; ++i) {
294 u_plus_AD[i] = ADNumberType(u_plus[i]);
295 u_minus_AD[i] = ADNumberType(u_minus[i]);
296 for (size_t d = 0; d < dim; ++d) {
297 grad_u_plus_AD[i][d] = ADNumberType(grad_u_plus[i][d]);
298 grad_u_minus_AD[i][d] = ADNumberType(grad_u_minus[i][d]);
299 }
300 }
301
302 std::array<dealii::Tensor<1, dim, ADNumberType>, n_components> F_AD_plus{}, F_AD_minus{};
303
304 std::array<JacobianMatrix<NumberType, n_components>, dim> J_plus{}, J_minus{};
305
306 for (size_t j = 0; j < n_components; ++j) {
307 seed(u_plus_AD[j]);
308 seed(u_minus_AD[j]);
309
310 F_AD_plus = {};
311 F_AD_minus = {};
312 model.flux(F_AD_plus, x_q, flux_tie(u_plus_AD, grad_u_plus_AD, extractors, variables, cell_width_plus));
313 model.flux(F_AD_minus, x_q, flux_tie(u_minus_AD, grad_u_minus_AD, extractors, variables, cell_width_minus));
314
315 for (size_t d = 0; d < dim; ++d) {
316 for (size_t i = 0; i < n_components; ++i) {
317 J_plus[d][i][j] = autodiff::derivative(F_AD_plus[i][d]);
318 J_minus[d][i][j] = autodiff::derivative(F_AD_minus[i][d]);
319
320 if (j == 0) {
321 result.F_plus[i][d] = F_AD_plus[i][d].val();
322 result.F_minus[i][d] = F_AD_minus[i][d].val();
323 }
324 }
325 }
326
327 unseed(u_plus_AD[j]);
328 unseed(u_minus_AD[j]);
329 }
330
331 // One speed per block, from the flux jacobian restricted to that block, and a component
332 // marked no_wave_speed gets none at all -- its numerical flux is then identically zero, so
333 // the reconstruction, and its slope limiter, never reaches its row. See
334 // AbstractModel::wave_speed_blocks for why a shared speed is wrong for a system that
335 // mixes a conservation law with constraints.
336 std::array<int, n_components> blocks;
337 model.wave_speed_blocks(blocks);
338
339 const auto a = per_block<n_components, std::array<NumberType, dim>>(blocks, [&](const int block) {
340 return WaveSpeedStrategy::template compute_speeds<NumberType, dim, n_components>(
343 });
344
345 for (size_t d = 0; d < dim; ++d)
346 for (size_t component = 0; component < n_components; ++component)
347 result.a_half[component][d] = a[component][d];
348
349 // A model that excludes a component and then writes a flux for it keeps the physical
350 // average (F^+ + F^-)/2 but loses the dissipation that stabilises it -- a central flux on
351 // a hyperbolic equation, which oscillates rather than failing outright. Catch the
352 // contradiction here instead of leaving it in the solution.
353 for (size_t i = 0; i < n_components; ++i)
354 if (blocks[i] < 0)
355 Assert(result.F_plus[i].norm() == NumberType(0) && result.F_minus[i].norm() == NumberType(0),
356 dealii::ExcMessage("A component marked no_wave_speed by wave_speed_blocks() wrote a flux."));
357
358 return result;
359 }
360
361 template <typename WaveSpeedStrategy, typename Model, typename NumberType, int dim, size_t n_components,
362 typename ExtractorArray, typename VariableVector>
364 const std::array<NumberType, n_components> &u_plus, const std::array<NumberType, n_components> &u_minus,
365 const dealii::Point<dim> &x_q, const double cell_width_plus, const double cell_width_minus,
366 const ExtractorArray &extractors, const VariableVector &variables, const Model &model)
367 {
369 return compute_kt_flux_and_speeds<WaveSpeedStrategy>(u_plus, u_minus, zero_grad, zero_grad, x_q,
370 cell_width_plus, cell_width_minus, extractors, variables,
371 model);
372 }
373
374 template <int dim, typename NumberType, size_t n_components>
375 std::array<dealii::Tensor<1, dim, NumberType>, n_components>
376 compute_numerical_flux(const std::array<dealii::Tensor<1, dim, NumberType>, n_components> &F_plus,
377 const std::array<dealii::Tensor<1, dim, NumberType>, n_components> &F_minus,
378 const std::array<dealii::Tensor<1, dim, NumberType>, n_components> &a_half,
379 const std::array<NumberType, n_components> &u_plus,
380 const std::array<NumberType, n_components> &u_minus)
381 {
382 std::array<dealii::Tensor<1, dim, NumberType>, n_components> H{};
383 for (size_t c = 0; c < n_components; ++c) {
384 H[c] = (F_plus[c] + F_minus[c]) * 0.5 - a_half[c] * (u_plus[c] - u_minus[c]) * 0.5;
385 }
386 return H;
387 }
388
389 } // namespace internal
390
391 namespace internal
392 {
393 template <int dim, typename NumberType, size_t n_components> struct KTNumFluxJacobianData {
394 std::array<SimpleMatrix<dealii::Tensor<1, dim, NumberType>, n_components>, 2> u{};
395 std::array<SimpleMatrix<dealii::Tensor<1, dim, dealii::Tensor<1, dim, NumberType>>, n_components>, 2> grad{};
396 };
397
398 template <typename WaveSpeedStrategy, typename Model, typename NumberType, int dim, size_t n_components,
399 typename ExtractorArray, typename VariableVector>
401 const std::array<NumberType, n_components> &u_plus, const std::array<NumberType, n_components> &u_minus,
403 const GradientType<dim, NumberType, n_components> &grad_u_minus, const dealii::Point<dim> &x_q,
404 const double cell_width_plus, const double cell_width_minus, const ExtractorArray &extractors,
405 const VariableVector &variables, const Model &model)
406 {
407 // 1. Compute the physical flux, its value/gradient Jacobians, and the second derivatives needed for da.
409 u_plus, grad_u_plus, x_q, cell_width_plus, extractors, variables, model);
411 u_minus, grad_u_minus, x_q, cell_width_minus, extractors, variables, model);
412
413 // 2. One wave speed per block, and its derivative, from the flux jacobian restricted to
414 // that block. This has to mirror compute_kt_flux_and_speeds exactly -- a residual and a
415 // jacobian that disagree about the dissipation are an inconsistent linearisation, which is
416 // worse than either choice on its own. See AbstractModel::wave_speed_blocks.
417 std::array<int, n_components> blocks;
418 model.wave_speed_blocks(blocks);
419
420 const auto a = per_block<n_components, std::array<NumberType, dim>>(blocks, [&](const int block) {
421 return WaveSpeedStrategy::template compute_speeds<NumberType, dim, n_components>(
424 });
425
426 // 3. Differentiate the selected physical wave speed with the AD flux Hessian.
427 using SpeedDerivatives = std::pair<std::array<std::array<NumberType, n_components>, dim>,
428 std::array<std::array<NumberType, n_components>, dim>>;
429 const auto da = per_block<n_components, SpeedDerivatives>(blocks, [&](const int block) {
430 return WaveSpeedStrategy::template compute_selected_speed_derivatives<NumberType, dim, n_components>(
435 });
436
437 // 4. Assemble j_numflux
439
440 for (size_t d = 0; d < dim; ++d) {
441 for (size_t i = 0; i < n_components; ++i) {
442 const NumberType du_i = u_plus[i] - u_minus[i];
443 const auto &[da_plus, da_minus] = da[i];
444 for (size_t c = 0; c < n_components; ++c) {
445 const NumberType delta_ic = (i == c) ? NumberType(1) : NumberType(0);
446
447 // dH_i^d / du_minus_c
448 j_numflux.u[0](i, c)[d] = NumberType(0.5) * minus.J[d][i][c] + NumberType(0.5) * a[i][d] * delta_ic -
449 NumberType(0.5) * du_i * da_minus[d][c];
450
451 // dH_i^d / du_plus_c
452 j_numflux.u[1](i, c)[d] = NumberType(0.5) * plus.J[d][i][c] - NumberType(0.5) * a[i][d] * delta_ic -
453 NumberType(0.5) * du_i * da_plus[d][c];
454 }
455 }
456 }
457
458 for (size_t d_in = 0; d_in < dim; ++d_in) {
459 const auto da_grad = per_block<n_components, SpeedDerivatives>(blocks, [&](const int block) {
460 return WaveSpeedStrategy::template compute_selected_speed_derivatives<NumberType, dim, n_components>(
465 });
466 for (size_t d_out = 0; d_out < dim; ++d_out)
467 for (size_t i = 0; i < n_components; ++i) {
468 const NumberType du_i = u_plus[i] - u_minus[i];
469 const auto &[da_grad_plus, da_grad_minus] = da_grad[i];
470 for (size_t c = 0; c < n_components; ++c) {
471 j_numflux.grad[0](i, c)[d_out][d_in] = NumberType(0.5) * minus.grad_J[i][c][d_out][d_in] -
472 NumberType(0.5) * du_i * da_grad_minus[d_out][c];
473 j_numflux.grad[1](i, c)[d_out][d_in] = NumberType(0.5) * plus.grad_J[i][c][d_out][d_in] -
474 NumberType(0.5) * du_i * da_grad_plus[d_out][c];
475 }
476 }
477 }
478
479 return j_numflux;
480 }
481
482 } // namespace internal
483
484 namespace internal
485 {
486 template <typename Model, typename NumberType, int dim, size_t n_components, typename ExtractorArray,
487 typename VariableVector>
488 std::array<dealii::Tensor<1, dim, NumberType>, n_components> compute_diffusion_flux(
489 const std::array<NumberType, n_components> &u_minus, const std::array<NumberType, n_components> &u_plus,
492 const ThirdDerivativeType<dim, NumberType, n_components> &third_derivatives_minus,
493 const ThirdDerivativeType<dim, NumberType, n_components> &third_derivatives_plus,
494 const dealii::Point<dim> &x_q, const double cell_width_minus, const double cell_width_plus,
495 const ExtractorArray &extractors, const VariableVector &variables, const Model &model)
496 {
497 std::array<dealii::Tensor<1, dim, NumberType>, n_components> D_minus{};
498 std::array<dealii::Tensor<1, dim, NumberType>, n_components> D_plus{};
499 std::array<dealii::Tensor<1, dim, NumberType>, n_components> D{};
500 model.diffusion_flux(D_minus, x_q,
501 diffusion_flux_tie(u_minus, grad_u_minus, third_derivatives_minus, extractors, variables,
502 cell_width_minus));
503 model.diffusion_flux(
504 D_plus, x_q,
505 diffusion_flux_tie(u_plus, grad_u_plus, third_derivatives_plus, extractors, variables, cell_width_plus));
506 for (size_t c = 0; c < n_components; ++c)
507 D[c] = NumberType(0.5) * (D_minus[c] + D_plus[c]);
508 return D;
509 }
510
511 template <int dim, typename NumberType, size_t n_components> struct DiffusionFluxJacobianData {
512 std::array<SimpleMatrix<dealii::Tensor<1, dim, NumberType>, n_components>, 2> u{};
513 std::array<SimpleMatrix<dealii::Tensor<1, dim, dealii::Tensor<1, dim, NumberType>>, n_components>, 2> grad{};
514 std::array<SimpleMatrix<dealii::Tensor<1, dim, dealii::Tensor<3, dim, NumberType>>, n_components>, 2>
516 };
517
518 template <typename Model, typename NumberType, int dim, size_t n_components, typename ExtractorArray,
519 typename VariableVector>
521 const std::array<NumberType, n_components> &u_minus, const std::array<NumberType, n_components> &u_plus,
524 const ThirdDerivativeType<dim, NumberType, n_components> &third_derivatives_minus,
525 const ThirdDerivativeType<dim, NumberType, n_components> &third_derivatives_plus,
526 const dealii::Point<dim> &x_q, const double cell_width_minus, const double cell_width_plus,
527 const ExtractorArray &extractors, const VariableVector &variables, const Model &model)
528 {
529 using ADNumberType = autodiff::Real<1, NumberType>;
530
532
533 std::array<ADNumberType, n_components> u_minus_AD{};
534 std::array<ADNumberType, n_components> u_plus_AD{};
535 std::array<dealii::Tensor<1, dim, ADNumberType>, n_components> grad_u_minus_AD{};
536 std::array<dealii::Tensor<1, dim, ADNumberType>, n_components> grad_u_plus_AD{};
537 ThirdDerivativeType<dim, ADNumberType, n_components> third_derivatives_minus_AD{};
539 for (size_t c = 0; c < n_components; ++c) {
540 u_minus_AD[c] = ADNumberType(u_minus[c]);
541 u_plus_AD[c] = ADNumberType(u_plus[c]);
542 for (size_t d = 0; d < dim; ++d) {
543 grad_u_minus_AD[c][d] = ADNumberType(grad_u_minus[c][d]);
544 grad_u_plus_AD[c][d] = ADNumberType(grad_u_plus[c][d]);
545 }
546 for (size_t d0 = 0; d0 < dim; ++d0)
547 for (size_t d1 = 0; d1 < dim; ++d1)
548 for (size_t d2 = 0; d2 < dim; ++d2) {
549 third_derivatives_minus_AD[c][d0][d1][d2] = ADNumberType(third_derivatives_minus[c][d0][d1][d2]);
550 third_derivatives_plus_AD[c][d0][d1][d2] = ADNumberType(third_derivatives_plus[c][d0][d1][d2]);
551 }
552 }
553
554 std::array<dealii::Tensor<1, dim, ADNumberType>, n_components> D_AD{};
555
556 for (size_t c = 0; c < n_components; ++c) {
557 seed(u_minus_AD[c]);
558 D_AD = {};
559 model.diffusion_flux(D_AD, x_q,
560 diffusion_flux_tie(u_minus_AD, grad_u_minus_AD, third_derivatives_minus_AD, extractors,
561 variables, cell_width_minus));
562 for (size_t i = 0; i < n_components; ++i)
563 for (size_t d = 0; d < dim; ++d)
564 result.u[0](i, c)[d] = NumberType(0.5) * derivative(D_AD[i][d]);
565 unseed(u_minus_AD[c]);
566
567 seed(u_plus_AD[c]);
568 D_AD = {};
569 model.diffusion_flux(D_AD, x_q,
570 diffusion_flux_tie(u_plus_AD, grad_u_plus_AD, third_derivatives_plus_AD, extractors,
571 variables, cell_width_plus));
572 for (size_t i = 0; i < n_components; ++i)
573 for (size_t d = 0; d < dim; ++d)
574 result.u[1](i, c)[d] = NumberType(0.5) * derivative(D_AD[i][d]);
575 unseed(u_plus_AD[c]);
576
577 for (size_t d_in = 0; d_in < dim; ++d_in) {
578 seed(grad_u_minus_AD[c][d_in]);
579 D_AD = {};
580 model.diffusion_flux(D_AD, x_q,
581 diffusion_flux_tie(u_minus_AD, grad_u_minus_AD, third_derivatives_minus_AD,
582 extractors, variables, cell_width_minus));
583 for (size_t i = 0; i < n_components; ++i)
584 for (size_t d_out = 0; d_out < dim; ++d_out)
585 result.grad[0](i, c)[d_out][d_in] = NumberType(0.5) * derivative(D_AD[i][d_out]);
586 unseed(grad_u_minus_AD[c][d_in]);
587
588 seed(grad_u_plus_AD[c][d_in]);
589 D_AD = {};
590 model.diffusion_flux(D_AD, x_q,
591 diffusion_flux_tie(u_plus_AD, grad_u_plus_AD, third_derivatives_plus_AD, extractors,
592 variables, cell_width_plus));
593 for (size_t i = 0; i < n_components; ++i)
594 for (size_t d_out = 0; d_out < dim; ++d_out)
595 result.grad[1](i, c)[d_out][d_in] = NumberType(0.5) * derivative(D_AD[i][d_out]);
596 unseed(grad_u_plus_AD[c][d_in]);
597 }
598
599 for (size_t d0 = 0; d0 < dim; ++d0)
600 for (size_t d1 = 0; d1 < dim; ++d1)
601 for (size_t d2 = 0; d2 < dim; ++d2) {
602 seed(third_derivatives_minus_AD[c][d0][d1][d2]);
603 D_AD = {};
604 model.diffusion_flux(D_AD, x_q,
605 diffusion_flux_tie(u_minus_AD, grad_u_minus_AD, third_derivatives_minus_AD,
606 extractors, variables, cell_width_minus));
607 for (size_t i = 0; i < n_components; ++i)
608 for (size_t d_out = 0; d_out < dim; ++d_out)
609 result.third_derivatives[0](i, c)[d_out][d0][d1][d2] =
610 NumberType(0.5) * derivative(D_AD[i][d_out]);
611 unseed(third_derivatives_minus_AD[c][d0][d1][d2]);
612
613 seed(third_derivatives_plus_AD[c][d0][d1][d2]);
614 D_AD = {};
615 model.diffusion_flux(D_AD, x_q,
616 diffusion_flux_tie(u_plus_AD, grad_u_plus_AD, third_derivatives_plus_AD,
617 extractors, variables, cell_width_plus));
618 for (size_t i = 0; i < n_components; ++i)
619 for (size_t d_out = 0; d_out < dim; ++d_out)
620 result.third_derivatives[1](i, c)[d_out][d0][d1][d2] =
621 NumberType(0.5) * derivative(D_AD[i][d_out]);
622 unseed(third_derivatives_plus_AD[c][d0][d1][d2]);
623 }
624 }
625
626 return result;
627 }
628
629 } // namespace internal
630
631 // Model_ keeps its second place and merely gains a default, rather than moving to the end.
632 // Moving it would let an application that names a Reconstructor drop it, but it would also
633 // force every *model-generic* discretization -- one Discretization built from a bare
634 // ComponentDescriptor and shared across several models, which is how most of the regression
635 // tests are written -- to spell out all four preceding arguments to reach the model.
636 template <typename Discretization_,
638 def::HasReconstructor Reconstructor_ =
640 def::HasWaveSpeed WaveSpeedStrategy_ = MaxEigenvalueWaveSpeed,
641 def::HasReconstructor JacobianReconstructor_ = Reconstructor_>
643 class Assembler : public AbstractAssembler<typename Discretization_::VectorType,
644 typename Discretization_::SparseMatrixType, Discretization_::dim>
645 {
646 protected:
647 // Placeholder for the "extractors" slot of e_tie() when the extractors are being computed and so cannot
648 // be passed to themselves. Same trick as DiFfRG::FEMAssembler.
649 constexpr static int nothing = 0;
650
662 template <typename... T> auto fv_tie(T &&...t)
663 {
664 return named_tuple<std::tuple<T &...>,
665 StringSet<"fe_functions", "fe_derivatives", "extractors", "variables", "cell_width">>(
666 std::tie(t...));
667 }
668
669 template <typename... T> static constexpr auto v_tie(T &&...t)
670 {
671 return named_tuple<std::tuple<T &...>, StringSet<"variables", "extractors">>(std::tie(t...));
672 }
673
674 template <typename... T> static constexpr auto e_tie(T &&...t)
675 {
676 return named_tuple<std::tuple<T &...>,
677 StringSet<"fe_functions", "fe_derivatives", "fe_hessians", "extractors", "variables",
678 "potential", "potential_gradient", "potential_hessian">>(std::tie(t...));
679 }
680
681 public:
682 using Discretization = Discretization_;
683 using Model = Model_;
684 using Reconstructor = Reconstructor_;
685 using WaveSpeedStrategy = WaveSpeedStrategy_;
686 using JacobianReconstructor = JacobianReconstructor_;
690
692 static constexpr uint dim = Discretization::dim;
693 static_assert(Reconstructor::dim == dim, "Reconstructor dimension must match the discretization dimension.");
694 static_assert(JacobianReconstructor::dim == dim,
695 "JacobianReconstructor dimension must match the discretization dimension.");
696 static constexpr uint n_components = Components::count_fe_functions(0);
697 static constexpr uint n_faces = GeometryInfo<dim>::faces_per_cell;
698 // using CacheData = internal::Cache_Data<NumberType, dim, n_components>;
701 using Iterator = typename DoFHandler<Discretization::dim>::active_cell_iterator;
702 using Point = dealii::Point<dim>;
705 std::vector<types::global_dof_index> to_dofs;
706 std::vector<types::global_dof_index> from_dofs;
707 };
709 unsigned int cell_index = 0;
710 unsigned int face_index = 0;
711 bool boundary = false;
712 std::optional<unsigned int> neighbor_index;
713 std::optional<unsigned int> neighbor_face_index;
715 };
718 std::array<internal::BoundaryReconstructionStencilTopologyData<dim, n_components>, n_faces>
720 std::array<FaceJacobianDependencyCacheEntry, n_faces> face_jacobian_dependencies{};
722 std::vector<Point> quadrature_points;
723 std::vector<NumberType> jxw;
726 double cell_width = 0.;
727 };
730 dof_handler(discretization.get_dof_handler()), mapping(discretization.get_mapping()),
731 triangulation(discretization.get_triangulation()), fe(discretization.get_fe()),
733 EoM_cell(*(dof_handler.active_cell_iterators().end())),
734 old_EoM_cell(*(dof_handler.active_cell_iterators().end())),
735 EoM_config(DiFfRG::internal::resolve_eom_config(dof_handler, Config::EoMConfig(config))),
736 quadrature(1 + config.get_uint("/discretization/overintegration", 0)),
737 quadrature_face(1 + config.get_uint("/discretization/overintegration", 0)),
738 diagnose_flux_conditioning(config.get_bool("/discretization/diagnose_flux_conditioning", false))
739 {
740 AssertThrow(fe.dofs_per_cell == n_components,
741 ExcMessage("FV Kurganov-Tadmor assembler expects one dof per component."));
742 for (uint i = 0; i < n_components; ++i)
743 local_component_of_dof[i] = fe.system_to_component_index(i).first;
744
745 reinit();
746 }
747
748 virtual void reinit_vector(VectorType &vec) const override
749 {
750 reinit_la_vector(vec, discretization.get_locally_owned_dofs(), discretization.get_communicator());
751 }
752
753 virtual void reinit_matrix(SparseMatrixType &matrix) const override
754 {
755 reinit_la_matrix(matrix, get_sparsity_pattern_jacobian(), discretization.get_locally_owned_dofs(),
756 discretization.get_communicator());
757 }
758
759 virtual MPI_Comm get_communicator() const override { return discretization.get_communicator(); }
760
761 virtual void reinit_solution_view(SolutionView<VectorType> &view) const override
762 {
763 view.reinit(discretization.get_locally_owned_dofs(), discretization.get_locally_relevant_dofs(),
764 discretization.get_communicator());
765 }
766
767 virtual IndexSet get_differential_indices() const override
768 {
769 ComponentMask component_mask(model.template differential_components<dim>());
770 // See FEMAssembler::get_differential_indices for why this is restricted to owned rows.
771 return restrict_to_owned<VectorType>(DoFTools::extract_dofs(dof_handler, component_mask),
772 discretization.get_locally_owned_dofs());
773 }
774
775 virtual void attach_data_output(OutputFrame<dim, VectorType> &data_out, const VectorType &solution,
776 const VectorType &variables, const VectorType &dt_solution = VectorType(),
777 const VectorType &residual = VectorType()) override
778 {
779 const auto fe_function_names = Components::FEFunction_Descriptor::get_names_vector();
780 std::vector<std::string> fe_function_names_residual;
781 for (const auto &name : fe_function_names)
782 fe_function_names_residual.push_back(name + "_residual");
783 std::vector<std::string> fe_function_names_dot;
784 for (const auto &name : fe_function_names)
785 fe_function_names_dot.push_back(name + "_dot");
786
787 auto fe_out = data_out.fields();
788 fe_out.attach(dof_handler, solution, fe_function_names);
789 if (dt_solution.size() > 0) fe_out.attach(dof_handler, dt_solution, fe_function_names_dot);
790 if (residual.size() > 0) fe_out.attach(dof_handler, residual, fe_function_names_residual);
791
792 readouts(data_out, solution, variables);
793 }
794
795 virtual void reinit() override
796 {
797 Timer timer;
798
800 const AffineConstraintContext<Components, dim> context(metadata);
801
802 auto &constraints = discretization.get_constraints();
803 constraints.clear();
804 DoFTools::make_hanging_node_constraints(dof_handler, constraints);
806 constraints.close();
807
808 // Mass sparsity pattern
809 {
810 DynamicSparsityPattern dsp(discretization.get_locally_relevant_dofs());
811 DoFTools::make_sparsity_pattern(dof_handler, dsp, constraints, /*keep_constrained_dofs = */ true);
813 discretization.get_locally_relevant_dofs(),
814 discretization.get_communicator());
816 discretization.get_communicator());
817 MatrixCreator::create_mass_matrix(dof_handler, quadrature, mass_matrix,
818 static_cast<Function<dim, NumberType> *>(nullptr), constraints);
819 }
820 // // Jacobian sparsity pattern
821 // {
822 // DynamicSparsityPattern dsp(dof_handler.n_dofs());
823 // DoFTools::make_flux_sparsity_pattern(dof_handler, dsp, discretization.get_constraints(),
824 // /*keep_constrained_dofs = */ true);
825 // sparsity_pattern_jacobian.copy_from(dsp);
826 // }
827
828 // Hoisted out of probe_diffusion_flux_conditioning(): GridTools::diameter is COLLECTIVE
829 // on a partitioned triangulation, and that function is entered conditionally
830 // (diagnose_flux_conditioning, and only on the first residual assembly). A collective
831 // behind a condition is a hang waiting for the condition to stop agreeing across ranks.
832 // Here every rank arrives unconditionally, and the value cannot go stale because KT
833 // refuses to run under mesh adaptivity.
834 domain_diameter = GridTools::diameter(triangulation);
835
837
843
844 timings_reinit.push_back(timer.wall_time());
845 }
846
847 virtual void set_time(double t) override { model.set_time(t); }
848
853 virtual const SparseMatrixType &get_mass_matrix() const override { return mass_matrix; }
854
855 virtual void residual_variables(VectorType &residual, const VectorType &variables,
856 const VectorType &spatial_solution) override
857 {
858 // Mirrors DiFfRG::FEMAssembler::residual_variables: run the extractors at the EoM first, then hand
859 // dt_variables the v_tie(variables, extractors) tuple. Without the extractors a model whose
860 // Variables flow depends on its FE solution cannot be assembled.
861 std::array<NumberType, Components::count_extractors()> __extracted_data{{}};
862 if constexpr (Components::count_extractors() > 0)
863 extract(__extracted_data, spatial_solution, variables, true, false, false);
864 const auto &extracted_data = __extracted_data;
865 model.dt_variables(residual, v_tie(variables, extracted_data));
866 };
867
868 virtual void jacobian_variables([[maybe_unused]] FullMatrix<NumberType> &jacobian,
869 [[maybe_unused]] const VectorType &variables, const VectorType &) override
870 {
871 static_assert(true, "jacobian_variables is not implemented for this model");
872 // model.template jacobian_variables<0>(jacobian, fv_tie(variables));
873 };
874
876 std::array<NumberType, n_components> values{};
878 std::array<Tensor<2, dim, NumberType>, n_components> hessians{};
879 };
880
881 void readouts(OutputFrame<dim, VectorType> &data_out, const VectorType &solution_global,
882 const VectorType &variables) const
883 {
884 auto raw_potential = reconstruct_raw_potential(
885 solution_global, dof_handler, mapping,
886 [&](const auto &p, const auto &values) { return model.raw_potential_gradient(p, values); }, EoM_config,
888 auto helper = [&](auto &&...args) {
889 if constexpr (sizeof...(args) == 3) {
890 auto &&[id, EoMfun, outputter] = std::forward_as_tuple(std::forward<decltype(args)>(args)...);
891 data_out.register_readout(id);
892 auto EoM_cell = this->EoM_cell;
893 auto EoM_result = get_EoM_point_with_potential(
894 EoM_cell, solution_global, dof_handler, mapping, EoMfun,
895 [](const auto &point, const auto &) { return point; }, EoM_config, EoM_minimum_guess,
897 if (EoM_result.potential) EoM_minimum_guess = EoM_result.potential->minimum;
898 const auto EoM = EoM_result.point;
899 this->EoM_cell = EoM_cell;
900
901 auto solution = reconstruct_readout_solution(EoM_cell, solution_global, EoM);
902 const auto potential = evaluate_raw_potential(raw_potential, mapping, EoM);
903
904 // The readout is always at this readout's EoM. The extractors may not be: a model that
905 // defines extractor_point reads them elsewhere, and dt_variables must see the same
906 // values here as it does during assembly.
907 std::array<NumberType, Components::count_extractors()> extracted_data{{}};
908 if constexpr (Components::count_extractors() > 0) {
909 const auto [x, cell] = resolve_extractor_point(EoM, EoM_cell, solution_global);
910 const auto extractor_solution =
911 reconstruct_readout_solution(cell, solution_global, x, /*with_hessians=*/true);
912 const auto extractor_potential = evaluate_raw_potential(raw_potential, mapping, x);
913 model.extract(extracted_data, x,
914 e_tie(extractor_solution.values, extractor_solution.gradients,
915 extractor_solution.hessians, nothing, variables, extractor_potential.value,
916 extractor_potential.gradient, extractor_potential.mass_hessian));
917 }
918 outputter(data_out, EoM,
919 e_tie(solution.values, solution.gradients, solution.hessians, extracted_data, variables,
920 potential.value, potential.gradient, potential.mass_hessian));
921 data_out.attach_eom_potential(std::move(EoM_result));
922 } else {
924 }
925 };
926 model.readouts_multiple(helper, data_out);
927 data_out.attach_raw_potential(std::move(raw_potential));
928 }
929
949 std::pair<Point, Iterator> resolve_extractor_point(const Point &EoM_point, const Iterator &EoM_cell_,
950 [[maybe_unused]] const VectorType &solution_global) const
951 {
953 // One dof per cell, so the value at the cell centre is that dof -- no reconstruction
954 // needed, and none wanted: this runs on every residual evaluation, and a per-cell
955 // stencil fill over the whole mesh is serial work that would dominate the assembly.
956 std::vector<types::global_dof_index> cell_dofs(n_components);
958 [&](const Iterator &cell, const Point &,
959 std::vector<NumberType> &values,
960 std::vector<Tensor<1, dim, NumberType>> &) {
961 cell->get_dof_indices(cell_dofs);
962 for (uint c = 0; c < n_components; ++c)
963 values[c] = solution_global[cell_dofs[c]];
964 });
965 sample.compute_central_difference_gradients();
966 const auto point = model.template extractor_point<dim, NumberType>(EoM_point, sample);
967 if (point == EoM_point) return {EoM_point, EoM_cell_};
968 return {point, GridTools::find_active_cell_around_point(dof_handler, point)};
969 } else
970 return {EoM_point, EoM_cell_};
971 }
972
974 const Point &x, bool with_hessians = false) const
975 {
976 CellStencilData stencil;
977 fill_cell_stencil(cell, solution_global, stencil);
978
979 const auto gradients = Reconstructor::template compute_gradient<n_components>(
980 stencil.cell.x, stencil.cell.u, stencil.neighbors.x, stencil.neighbors.u);
981
982 ReadoutSolution solution;
983 solution.values = internal::reconstruct_u(stencil.cell.u, stencil.cell.x, x, gradients);
984 solution.gradients = Reconstructor::template compute_gradient_at_point<n_components>(
985 stencil.cell.x, x, stencil.cell.u, stencil.neighbors.x, stencil.neighbors.u);
986
987 if (with_hessians) {
988 // Second derivative from the quadratic through the same three cell averages the gradient uses:
989 // with s measured from the cell centre and u(s) = u_C + a s + b s^2 through (dx_1, u_1), (0, u_C),
990 // (dx_2, u_2), one gets u'' = 2b = 2 (du_2 - du_1) / (dx_2 - dx_1), where du_i are exactly the
991 // one-sided slopes of compute_gradient. Being a quadratic fit, u'' is constant over the cell, so
992 // it does not matter that it is not evaluated at `x` itself.
993 //
994 // Deliberately UNLIMITED: the limiter exists to keep the reconstructed *slope* monotone for the
995 // scheme, and applying it to a curvature would bias it toward zero exactly where the potential is
996 // most curved. The price is that this is the noisiest quantity available near a steep front --
997 // acceptable because nothing in the flux consumes it.
998 //
999 // Only the diagonal d^2/dx_d^2 entries are filled: the 2*dim stencil has no corner neighbours, so
1000 // mixed derivatives are not available (harmless for the 1D field-space this assembler targets).
1001 for (uint c = 0; c < n_components; ++c)
1002 for (int d = 0, i_n_1 = 0, i_n_2 = 1; d < dim; ++d, i_n_1 += 2, i_n_2 += 2) {
1003 const auto dx_1 = stencil.neighbors.x[i_n_1][d] - stencil.cell.x[d];
1004 const auto dx_2 = stencil.neighbors.x[i_n_2][d] - stencil.cell.x[d];
1005 const auto du_1 = (stencil.neighbors.u[i_n_1][c] - stencil.cell.u[c]) / dx_1;
1006 const auto du_2 = (stencil.neighbors.u[i_n_2][c] - stencil.cell.u[c]) / dx_2;
1007 solution.hessians[c][d][d] = NumberType(2.) * (du_2 - du_1) / (dx_2 - dx_1);
1008 }
1009 }
1010 return solution;
1011 }
1012
1038 auto extractor_raw_potential(const VectorType &solution_global) const
1039 {
1040 if constexpr (Model::extract_uses_potential)
1042 solution_global, dof_handler, mapping,
1043 [&](const auto &p, const auto &values) { return model.raw_potential_gradient(p, values); }, EoM_config,
1045 else
1046 return UnusedPotential{};
1047 }
1048
1049 void extract(std::array<NumberType, Components::count_extractors()> &data, const VectorType &solution_global,
1050 const VectorType &variables, bool search_EoM, bool set_EoM, bool postprocess) const
1051 {
1052 auto EoM = this->EoM;
1053 auto EoM_cell = this->EoM_cell;
1054 if (search_EoM || EoM_cell == *(dof_handler.active_cell_iterators().end())) {
1055 auto EoM_result = get_EoM_point_with_potential(
1056 EoM_cell, solution_global, dof_handler, mapping,
1057 [&](const auto &p, const auto &values) { return model.EoM(p, values); },
1058 [&](const auto &p, const auto &values) { return postprocess ? model.EoM_postprocess(p, values) : p; },
1060 EoM = EoM_result.point;
1061 if (EoM_result.potential) EoM_minimum_guess = EoM_result.potential->minimum;
1062 }
1063 if (set_EoM) {
1064 this->EoM = EoM;
1065 this->EoM_cell = EoM_cell;
1066 }
1067
1068 const auto [x, cell] = resolve_extractor_point(EoM, EoM_cell, solution_global);
1069 auto solution = reconstruct_readout_solution(cell, solution_global, x, /*with_hessians=*/true);
1070 const auto raw_potential = extractor_raw_potential(solution_global);
1071 const auto potential = evaluate_raw_potential(raw_potential, mapping, x);
1072 model.extract(data, x,
1073 e_tie(solution.values, solution.gradients, solution.hessians, nothing, variables,
1074 potential.value, potential.gradient, potential.mass_hessian));
1075 }
1076
1077 virtual void mass(VectorType &mass, const VectorType &solution_global, const VectorType &solution_global_dot,
1078 NumberType weight) override
1079 {
1080 using CopyData = internal::CopyData_R<NumberType>;
1081 const auto &constraints = discretization.get_constraints();
1082
1083 Scratch scratch_data(quadrature);
1084 CopyData copy_data;
1085
1086 const auto cell_worker = [&](const Iterator &cell, Scratch &scratch_data, CopyData &copy_data) {
1087 const auto &cell_geometry = get_cell_topology(cell);
1088 constexpr uint n_dofs = n_components;
1089
1090 copy_data.reinit(cell, n_dofs);
1091
1092 fill_constant_quadrature_values(cell, solution_global, solution_global_dot, scratch_data);
1093
1094 std::array<NumberType, n_components> mass_values{};
1095 for (size_t q_index = 0; q_index < cell_geometry.quadrature_points.size(); ++q_index) {
1096 const auto &x_q = cell_geometry.quadrature_points[q_index];
1097 model.mass(mass_values, x_q, scratch_data.solution_values[q_index],
1098 scratch_data.solution_dot_values[q_index]);
1099
1100 for (uint i = 0; i < n_dofs; ++i) {
1101 const auto component_i = local_component_of_dof[i];
1102 copy_data.cell_mass(i) +=
1103 weight * cell_geometry.jxw[q_index] * mass_values[component_i]; // +phi_i(x_q) * mass(x_q, u_q)
1104 }
1105 }
1106 };
1107
1108 const auto copier = [&](const CopyData &c) {
1109 constraints.distribute_local_to_global(c.cell_mass, c.local_dof_indices, mass);
1110 };
1111
1112 MeshWorker::AssembleFlags flags = MeshWorker::assemble_own_cells;
1113
1114 // map() is collective and each rank visits only its own cells; see NoMapsHere.
1115 const NoMapsHere no_maps_during_assembly;
1116 const auto schedule = schedule_for(assembly_cost::local_fe);
1117 MeshWorker::mesh_loop(locally_owned_cells(dof_handler), cell_worker, copier, scratch_data, copy_data, flags,
1118 nullptr, nullptr, schedule.queue_length, schedule.chunk_size);
1119 // Resolve contributions this rank made to rows it does not own. A partition-boundary
1120 // face is assembled by exactly one of its two neighbours (mesh_loop hands it to the
1121 // smaller subdomain id), and that rank writes BOTH sides -- so the other side's rows
1122 // arrive here. A no-op for the serial types.
1123 mass.compress(dealii::VectorOperation::add);
1124 }
1125
1134
1136 dealii::Tensor<1, dim> normal(const Iterator &cell, const unsigned int face_index) const
1137 {
1138 return Assembler::face_normal_from_cell(cell, face_index);
1139 }
1140
1141 double jxw(const Iterator &cell, const unsigned int face_index) const
1142 {
1143 return Assembler::face_jxw(cell, face_index);
1144 }
1145 };
1146
1156
1158
1159 template <HasAssemblyContextView Context>
1160 void run_fv_kt_pre_assembly_hook(const AssemblyStage stage, const Context &context)
1161 {
1162 dispatch_fv_kt_pre_assembly(model, stage, context);
1163 }
1164
1165 static void fill_cell_data(const Iterator &cell, const VectorType &solution_global,
1166 std::vector<types::global_dof_index> &scratch_dof_indices, CellData &data)
1167 {
1168 data.x = cell->center();
1169 cell->get_dof_indices(scratch_dof_indices);
1170 for (unsigned int i = 0; i < n_components; ++i) {
1171 data.dof_indices[i] = scratch_dof_indices[i];
1172 data.u[i] = solution_global(scratch_dof_indices[i]);
1173 }
1174 }
1175
1176 static bool is_physical_boundary_face(const Iterator &cell, const unsigned int face_index)
1177 {
1178 return cell->at_boundary(face_index);
1179 }
1180
1181 static Iterator face_neighbor(const Iterator &cell, const unsigned int face_index)
1182 {
1183 return cell->neighbor(face_index);
1184 }
1185
1186 static void fill_cell_stencil(const Iterator &cell, const VectorType &solution_global, const Model &model,
1187 std::vector<types::global_dof_index> &scratch_dof_indices,
1188 CellStencilData &stencil)
1189 {
1190 stencil.neighbors = {};
1191 stencil.boundary_ids.fill(numbers::invalid_boundary_id);
1192 stencil.face_centers = {};
1193 for (auto &neighbor_dof_indices : stencil.neighbors.dof_indices)
1194 neighbor_dof_indices.fill(numbers::invalid_dof_index);
1195
1196 fill_cell_data(cell, solution_global, scratch_dof_indices, stencil.cell);
1197
1198 for (const auto face_index : cell->face_indices()) {
1199 const auto face = cell->face(face_index);
1200 stencil.face_centers[face_index] = face->center();
1201 if (is_physical_boundary_face(cell, face_index)) {
1202 stencil.boundary_ids[face_index] = face->boundary_id();
1203 if constexpr (dim == 1) {
1204 auto boundary_stencil =
1205 build_boundary_stencil<NumberType>(cell, face_index, solution_global, scratch_dof_indices);
1206 const bool boundary_supported =
1207 model.apply_boundary_stencil(boundary_stencil.u, boundary_stencil.x, face->center());
1208 AssertThrow(
1209 boundary_supported,
1210 ExcMessage("KT boundary stencil was rejected while populating a boundary-adjacent cell stencil."));
1211
1212 stencil.neighbors.x[face_index] = boundary_stencil.x[boundary_stencil.ghost_center];
1213 stencil.neighbors.u[face_index] = boundary_stencil.u[boundary_stencil.ghost_center];
1214 stencil.neighbors.dof_indices[face_index] = boundary_stencil.dof_indices[boundary_stencil.ghost_center];
1215 }
1216 continue;
1217 }
1218
1219 const auto neighbor = face_neighbor(cell, face_index);
1220 CellData neighbor_data;
1221 fill_cell_data(neighbor, solution_global, scratch_dof_indices, neighbor_data);
1222 stencil.neighbors.x[face_index] = neighbor_data.x;
1223 stencil.neighbors.u[face_index] = neighbor_data.u;
1224 stencil.neighbors.dof_indices[face_index] = neighbor_data.dof_indices;
1225 }
1226 }
1227
1228 template <typename BoundaryNumberType>
1229 requires(dim == 1)
1231 build_boundary_stencil(const Iterator &cell, const unsigned int boundary_face_no,
1232 const VectorType &solution_global,
1233 std::vector<types::global_dof_index> &scratch_dof_indices)
1234 {
1235 static_assert(dim == 1, "Paper-style boundary stencils currently support only dim=1.");
1236
1237 using BoundaryIndex = internal::BoundaryStencilIndex<dim>;
1238 const auto interior_face = GeometryInfo<1>::opposite_face[boundary_face_no];
1239 AssertThrow(!is_physical_boundary_face(cell, interior_face),
1240 ExcMessage("KT boundary stencil requires at least two interior cells behind the boundary face."));
1241
1243 boundary_stencil.lower_boundary = boundary_face_no == 0;
1244 boundary_stencil.cell_face = boundary_face_no;
1245 boundary_stencil.ghost_center =
1246 boundary_stencil.lower_boundary ? BoundaryIndex::lower_inner : BoundaryIndex::upper_inner;
1247 boundary_stencil.ghost_left =
1248 boundary_stencil.lower_boundary ? BoundaryIndex::lower_outer : BoundaryIndex::physical_cell;
1249 boundary_stencil.ghost_right =
1250 boundary_stencil.lower_boundary ? BoundaryIndex::physical_cell : BoundaryIndex::upper_outer;
1251 for (auto &dofs : boundary_stencil.dof_indices)
1252 dofs.fill(numbers::invalid_dof_index);
1253
1254 CellData cell_data;
1255 fill_cell_data(cell, solution_global, scratch_dof_indices, cell_data);
1256 boundary_stencil.x[BoundaryIndex::physical_cell] = cell_data.x;
1257 boundary_stencil.u[BoundaryIndex::physical_cell] = cell_data.u;
1258 boundary_stencil.dof_indices[BoundaryIndex::physical_cell] = cell_data.dof_indices;
1259
1260 auto neighbor = face_neighbor(cell, interior_face);
1261 CellData first_interior;
1262 fill_cell_data(neighbor, solution_global, scratch_dof_indices, first_interior);
1263
1264 AssertThrow(!is_physical_boundary_face(neighbor, interior_face),
1265 ExcMessage("KT boundary stencil requires a second interior cell behind the boundary face."));
1266 auto next_neighbor = face_neighbor(neighbor, interior_face);
1267 CellData second_interior;
1268 fill_cell_data(next_neighbor, solution_global, scratch_dof_indices, second_interior);
1269
1270 if (boundary_stencil.lower_boundary) {
1271 boundary_stencil.x[BoundaryIndex::upper_inner] = first_interior.x;
1272 boundary_stencil.x[BoundaryIndex::upper_outer] = second_interior.x;
1273 boundary_stencil.u[BoundaryIndex::upper_inner] = first_interior.u;
1274 boundary_stencil.u[BoundaryIndex::upper_outer] = second_interior.u;
1275 boundary_stencil.dof_indices[BoundaryIndex::upper_inner] = first_interior.dof_indices;
1276 boundary_stencil.dof_indices[BoundaryIndex::upper_outer] = second_interior.dof_indices;
1277 } else {
1278 boundary_stencil.x[BoundaryIndex::lower_inner] = first_interior.x;
1279 boundary_stencil.x[BoundaryIndex::lower_outer] = second_interior.x;
1280 boundary_stencil.u[BoundaryIndex::lower_inner] = first_interior.u;
1281 boundary_stencil.u[BoundaryIndex::lower_outer] = second_interior.u;
1282 boundary_stencil.dof_indices[BoundaryIndex::lower_inner] = first_interior.dof_indices;
1283 boundary_stencil.dof_indices[BoundaryIndex::lower_outer] = second_interior.dof_indices;
1284 }
1285
1286 return boundary_stencil;
1287 }
1288
1289 void fill_cell_data_from_topology(const CellGeometryDofs &topology, const VectorType &solution_global,
1290 CellData &data) const
1291 {
1293 }
1294
1295 template <int boundary_dim, typename BoundaryNumberType>
1297 build_boundary_stencil_from_cache_impl(const Iterator &cell, const unsigned int boundary_face_no,
1298 const VectorType &solution_global) const
1299 {
1300 const auto &topology = get_cell_topology(cell).boundary_stencils[boundary_face_no].primary;
1302 topology, solution_global);
1303 }
1304
1305 template <typename BoundaryNumberType>
1307 build_boundary_stencil_from_cache(const Iterator &cell, const unsigned int boundary_face_no,
1308 const VectorType &solution_global) const
1309 {
1311 solution_global);
1312 }
1313
1314 template <typename BoundaryNumberType>
1316 build_boundary_reconstruction_stencil_from_cache(const Iterator &cell, const unsigned int boundary_face_no,
1317 const VectorType &solution_global) const
1318 {
1319 const auto &topology = get_cell_topology(cell).boundary_stencils[boundary_face_no];
1321 topology, solution_global);
1322 }
1323
1324 void fill_cell_stencil(const Iterator &cell, const VectorType &solution_global, CellStencilData &stencil) const
1325 {
1326 initialize_cell_stencil_topology(cell->active_cell_index(), stencil);
1327 refresh_cell_stencil_values(cell->active_cell_index(), solution_global, stencil);
1328 }
1329
1332 const VectorType &solution_global, const types::global_dof_index dof_j) const
1333 {
1334 auto tagged_stencil = internal::tag_cell_stencil_dofs<dim, NumberType, n_components>(cell_stencil, dof_j);
1335
1336 for (const auto face_index : cell->face_indices()) {
1337 if (!is_physical_boundary_face(cell, face_index)) continue;
1338
1339 const auto boundary_stencil =
1340 build_boundary_stencil_from_cache<NumberType>(cell, face_index, solution_global);
1341 auto tagged_boundary_stencil =
1343 internal::populate_boundary_neighbor_from_model_stencil(tagged_boundary_stencil, tagged_stencil, face_index,
1344 cell_stencil.face_centers[face_index], model);
1345 }
1346
1347 return tagged_stencil;
1348 }
1349
1350 unsigned int find_neighbor_face(const Iterator &cell, const Iterator &neighbor) const
1351 {
1352 for (const auto neighbor_face_index : neighbor->face_indices()) {
1353 if (is_physical_boundary_face(neighbor, neighbor_face_index)) continue;
1354 if (face_neighbor(neighbor, neighbor_face_index) == cell) return neighbor_face_index;
1355 }
1356 AssertThrow(false, ExcMessage("Could not find reciprocal KT neighbor face."));
1357 return 0;
1358 }
1359
1360 template <def::HasReconstructor ActiveReconstructor>
1362 SolutionReconstructionCache &cache) const
1363 {
1365 for (auto &valid_faces : cache.face_reconstruction_valid)
1366 valid_faces.fill(false);
1367
1368 refresh_solution_reconstruction_cache_values(solution_global, cache);
1369
1370 for (const auto &descriptor : face_reconstruction_descriptors) {
1371 const auto cell_index = descriptor.cell_index;
1372 const auto face_index = descriptor.face_index;
1373 const auto &x_q = descriptor.face_center;
1374
1375 if (descriptor.boundary) {
1376 const auto &topology = cell_topology_cache[cell_index].boundary_stencils[face_index];
1377 auto boundary_stencil =
1379 topology, solution_global);
1380 cache.face_reconstructions[cell_index][face_index] =
1382 boundary_stencil, cache.cell_stencils[cell_index], x_q, model);
1383 cache.face_reconstruction_valid[cell_index][face_index] = true;
1384 continue;
1385 }
1386
1387 Assert(descriptor.neighbor_index.has_value(), ExcInternalError());
1388 Assert(descriptor.neighbor_face_index.has_value(), ExcInternalError());
1389 const auto neighbor_index = *descriptor.neighbor_index;
1390 const auto neighbor_face_index = *descriptor.neighbor_face_index;
1391 AssertIndexRange(neighbor_index, cache.cell_stencils.size());
1392 AssertIndexRange(neighbor_face_index, n_faces);
1393
1395 cache.cell_stencils[cell_index], cache.cell_stencils[neighbor_index], x_q);
1396 cache.face_reconstructions[cell_index][face_index] = state;
1397 cache.face_reconstruction_valid[cell_index][face_index] = true;
1398 cache.face_reconstructions[neighbor_index][neighbor_face_index] =
1400 cache.face_reconstruction_valid[neighbor_index][neighbor_face_index] = true;
1401 }
1402 }
1403
1405 {
1406 const auto n_active_cells = triangulation.n_active_cells();
1407 if (cache.cell_stencils.size() != n_active_cells) {
1408 cache.cell_stencils.resize(n_active_cells);
1409 cache.topology_initialized = false;
1410 }
1411 if (cache.face_reconstructions.size() != n_active_cells) cache.face_reconstructions.resize(n_active_cells);
1412 if (cache.face_reconstruction_valid.size() != n_active_cells)
1413 cache.face_reconstruction_valid.resize(n_active_cells);
1415 }
1416
1418 {
1419 for (unsigned int cell_index = 0; cell_index < cache.cell_stencils.size(); ++cell_index)
1420 initialize_cell_stencil_topology(cell_index, cache.cell_stencils[cell_index]);
1421 cache.topology_initialized = true;
1422 }
1423
1424 void initialize_cell_stencil_topology(const unsigned int cell_index, CellStencilData &stencil) const
1425 {
1426 const auto &topology = cell_topology_cache[cell_index].stencil;
1427 stencil.boundary_ids = topology.boundary_ids;
1428 stencil.face_centers = topology.face_centers;
1429 stencil.cell.x = topology.cell.x;
1430 stencil.cell.dof_indices = topology.cell.dof_indices;
1431 stencil.neighbors.x = topology.neighbors.x;
1432 stencil.neighbors.dof_indices = topology.neighbors.dof_indices;
1433 }
1434
1436 SolutionReconstructionCache &cache) const
1437 {
1438 for (unsigned int cell_index = 0; cell_index < cache.cell_stencils.size(); ++cell_index)
1439 refresh_cell_stencil_values(cell_index, solution_global, cache.cell_stencils[cell_index]);
1440 }
1441
1442 void refresh_cell_stencil_values(const unsigned int cell_index, const VectorType &solution_global,
1443 CellStencilData &stencil) const
1444 {
1445 const auto &topology = cell_topology_cache[cell_index].stencil;
1446 for (unsigned int i = 0; i < n_components; ++i)
1447 stencil.cell.u[i] = solution_global(topology.cell.dof_indices[i]);
1448
1449 for (unsigned int face_index = 0; face_index < n_faces; ++face_index) {
1450 if (topology.boundary_ids[face_index] == numbers::invalid_boundary_id) {
1451 for (unsigned int i = 0; i < n_components; ++i) {
1452 const auto dof = topology.neighbors.dof_indices[face_index][i];
1453 stencil.neighbors.u[face_index][i] = solution_global(dof);
1454 }
1455 continue;
1456 }
1457
1459 cell_topology_cache[cell_index].boundary_stencils[face_index].primary, solution_global);
1460 internal::populate_boundary_neighbor_from_model_stencil(boundary_stencil, stencil, face_index,
1461 topology.face_centers[face_index], model);
1462 }
1463 }
1464
1466 {
1468 face_reconstruction_descriptors.reserve(triangulation.n_active_cells() * n_faces);
1469
1470 // Every cell, not just the owned ones: an owned cell's flux worker reads its neighbours'
1471 // descriptors, and the `neighbor_index < cell_index` tiebreak below needs both sides
1472 // present to pick a side consistently.
1473 for (const auto &cell : dof_handler.active_cell_iterators()) {
1474 const auto cell_index = cell->active_cell_index();
1475 for (const auto face_index : cell->face_indices()) {
1477 descriptor.cell_index = cell_index;
1478 descriptor.face_index = face_index;
1479 descriptor.boundary = is_physical_boundary_face(cell, face_index);
1480 descriptor.face_center = cell_topology_cache[cell_index].stencil.face_centers[face_index];
1481 if (descriptor.boundary) {
1482 face_reconstruction_descriptors.push_back(descriptor);
1483 continue;
1484 }
1485
1486 const auto neighbor = face_neighbor(cell, face_index);
1487 const auto neighbor_index = neighbor->active_cell_index();
1488 if (neighbor_index < cell_index) continue;
1489 descriptor.neighbor_index = neighbor_index;
1490 descriptor.neighbor_face_index = find_neighbor_face(cell, neighbor);
1491 face_reconstruction_descriptors.push_back(descriptor);
1492 }
1493 }
1494 }
1495
1497 const Iterator &cell,
1498 const unsigned int face_index) const
1499 {
1500 const auto cell_index = cell->active_cell_index();
1501 AssertIndexRange(cell_index, cache.face_reconstructions.size());
1502 AssertIndexRange(face_index, n_faces);
1503 AssertThrow(cache.face_reconstruction_valid[cell_index][face_index],
1504 ExcMessage("KT face reconstruction cache entry was not initialized."));
1505 return cache.face_reconstructions[cell_index][face_index];
1506 }
1507
1509 const VectorType &solution_global, const Point &x_q,
1510 Scratch &scratch_data) const
1511 {
1512 fill_cell_stencil(cell, solution_global, scratch_data.cell_stencil);
1513 fill_cell_stencil(ncell, solution_global, scratch_data.ncell_stencil);
1515 scratch_data.ncell_stencil, x_q);
1516 }
1517
1518 auto compute_boundary_face_reconstruction_from_cache(const Iterator &cell, const unsigned int face_no,
1519 const VectorType &solution_global, const Point &x_q) const
1520 {
1521 auto boundary_stencil =
1523 CellStencilData cell_stencil;
1524 fill_cell_stencil(cell, solution_global, cell_stencil);
1525 return internal::compute_boundary_face_reconstruction_state<Reconstructor>(boundary_stencil, cell_stencil,
1526 x_q, model);
1527 }
1528
1530 const VectorType &solution_global,
1531 const Point &x_q, Scratch &scratch_data) const
1532 {
1533 fill_cell_stencil(cell, solution_global, scratch_data.cell_stencil);
1534 fill_cell_stencil(ncell, solution_global, scratch_data.ncell_stencil);
1536 scratch_data.cell_stencil, scratch_data.ncell_stencil, x_q);
1537 }
1538
1539 auto compute_boundary_jacobian_face_reconstruction_from_cache(const Iterator &cell, const unsigned int face_no,
1540 const VectorType &solution_global,
1541 const Point &x_q) const
1542 {
1543 auto boundary_stencil =
1545 CellStencilData cell_stencil;
1546 fill_cell_stencil(cell, solution_global, cell_stencil);
1548 cell_stencil, x_q, model);
1549 }
1550
1570 template <typename ExtractorArray>
1572 const ExtractorArray &extractors, const VectorType &variables) const
1573 {
1574 // Probe the MODEL, not the current data. Using the solution's actual gradient fails
1575 // whenever the flow starts from flat initial data: the local variation is then ~0 and
1576 // every flux looks baseline-dominated. Instead perturb the gradient by a synthetic
1577 // scale built from the solution magnitude and the domain size, which is what the
1578 // gradient will grow to once the flow develops.
1579 std::array<dealii::Tensor<1, dim, NumberType>, n_components> D_actual{};
1580 std::array<dealii::Tensor<1, dim, NumberType>, n_components> D_baseline{};
1581 const ThirdDerivativeType zero_third{};
1582 const GradientType zero_grad{};
1583
1584 const double domain_size = std::max(domain_diameter, 1e-300);
1585 std::array<double, n_components> max_baseline{};
1586 std::array<double, n_components> max_variation{};
1587 double u_scale = 0.0;
1588 unsigned int sampled = 0;
1589
1590 // First pass: the scale of the solution itself.
1591 for (const auto &cell : dof_handler.active_cell_iterators()) {
1592 if (sampled >= flux_conditioning_max_samples) break;
1593 for (unsigned int f = 0; f < n_faces; ++f) {
1594 if (cell->at_boundary(f)) continue;
1595 const auto &reconstruction = get_cached_face_reconstruction(reconstruction_cache, cell, f);
1596 for (size_t c = 0; c < n_components; ++c)
1597 u_scale = std::max(u_scale, std::abs(static_cast<double>(reconstruction.diffusion_u_minus[c])));
1598 ++sampled;
1599 break;
1600 }
1601 }
1602
1603 // A unit fallback keeps the probe meaningful for a flow starting from u == 0.
1604 const NumberType probe_gradient = static_cast<NumberType>(std::max(u_scale, 1.0) / domain_size);
1605 GradientType probe_grad{};
1606 for (size_t c = 0; c < n_components; ++c)
1607 for (int d = 0; d < dim; ++d)
1608 probe_grad[c][d] = probe_gradient;
1609
1610 sampled = 0;
1611 for (const auto &cell : dof_handler.active_cell_iterators()) {
1612 if (sampled >= flux_conditioning_max_samples) break;
1613 for (unsigned int f = 0; f < n_faces; ++f) {
1614 if (cell->at_boundary(f)) continue;
1615 const auto x_q = cell->face(f)->center();
1616 const auto &reconstruction = get_cached_face_reconstruction(reconstruction_cache, cell, f);
1617 const double probe_width = get_cell_topology(cell).cell_width;
1618 model.diffusion_flux(D_actual, x_q,
1619 internal::diffusion_flux_tie(reconstruction.diffusion_u_minus, probe_grad,
1620 zero_third, extractors, variables, probe_width));
1621 model.diffusion_flux(D_baseline, x_q,
1622 internal::diffusion_flux_tie(reconstruction.diffusion_u_minus, zero_grad, zero_third,
1623 extractors, variables, probe_width));
1624 for (size_t c = 0; c < n_components; ++c) {
1625 max_baseline[c] = std::max(max_baseline[c], static_cast<double>(D_baseline[c].norm()));
1626 max_variation[c] =
1627 std::max(max_variation[c], static_cast<double>((D_actual[c] - D_baseline[c]).norm()));
1628 }
1629 ++sampled;
1630 break; // one face per cell is plenty
1631 }
1632 }
1633
1634 for (size_t c = 0; c < n_components; ++c) {
1635 if (!(max_variation[c] > 0.0) || !std::isfinite(max_baseline[c])) continue;
1636
1637 const double relative_noise =
1638 max_baseline[c] / max_variation[c] * std::numeric_limits<NumberType>::epsilon();
1639 if (relative_noise <= flux_conditioning_warn_threshold) continue;
1640
1641 const auto message = spdlog::fmt_lib::format(
1642 "FV/KT: the diffusion flux of component {} is dominated by a gradient-independent baseline: "
1643 "max |F(du=0)| = {:.3e} over the mesh, but max |F - F(du=0)| = only {:.3e}. Differencing it "
1644 "across faces leaves a relative round-off of {:.1e} in the residual, which caps the accuracy any "
1645 "implicit solver can reach (and will crush its step size if the tolerance is tighter). Subtract "
1646 "the zero-gradient value analytically inside diffusion_flux().",
1647 c, max_baseline[c], max_variation[c], relative_noise);
1648
1649 report_port.warn("{}", message);
1650 }
1651 }
1652
1653 void fill_constant_quadrature_values(const Iterator &cell, const VectorType &solution_global,
1654 const VectorType &solution_global_dot, Scratch &scratch_data) const
1655 {
1656 fill_cell_data_from_topology(get_cell_topology(cell).stencil.cell, solution_global,
1657 scratch_data.cell_stencil.cell);
1658 for (auto &values : scratch_data.solution_values)
1659 for (uint c = 0; c < n_components; ++c)
1660 values[c] = scratch_data.cell_stencil.cell.u[c];
1661
1662 for (auto &values_dot : scratch_data.solution_dot_values)
1663 for (uint c = 0; c < n_components; ++c)
1664 values_dot[c] = solution_global_dot(scratch_data.cell_stencil.cell.dof_indices[c]);
1665 }
1666
1677 template <def::HasReconstructor ActiveReconstructor>
1678 static GradientType source_gradient(const CellStencilData &stencil, const Point &x_q)
1679 {
1680 return ActiveReconstructor::template compute_gradient_at_point<n_components>(
1681 stencil.cell.x, x_q, stencil.cell.u, stencil.neighbors.x, stencil.neighbors.u);
1682 }
1683
1684 static std::array<internal::GradientType<dim, NumberType, n_components>, n_faces>
1685 compute_neighbor_gradients(const Iterator &cell, const VectorType &solution_global, const Model &model,
1686 std::vector<types::global_dof_index> &scratch_dof_indices,
1687 CellStencilData &temporary_stencil)
1688 {
1689 std::array<internal::GradientType<dim, NumberType, n_components>, n_faces> gradients{};
1690
1691 for (const auto face_index : cell->face_indices()) {
1692 if (is_physical_boundary_face(cell, face_index)) continue;
1693
1694 const auto neighbor = face_neighbor(cell, face_index);
1695 fill_cell_stencil(neighbor, solution_global, model, scratch_dof_indices, temporary_stencil);
1696
1697 gradients[face_index] = Reconstructor::template compute_gradient<n_components>(
1698 temporary_stencil.cell.x, temporary_stencil.cell.u, temporary_stencil.neighbors.x,
1699 temporary_stencil.neighbors.u);
1700 }
1701
1702 return gradients;
1703 }
1704
1705 static Tensor<1, dim> face_normal_from_cell(const Iterator &cell, const unsigned int face_no)
1706 {
1707 const auto face_offset = cell->face(face_no)->center() - cell->center();
1708 const auto norm = face_offset.norm();
1709 AssertThrow(norm > 0., ExcMessage("Degenerate FV face normal."));
1710 return face_offset / norm;
1711 }
1712
1713 static double face_jxw(const Iterator &cell, const unsigned int face_no)
1714 {
1715 if constexpr (dim == 1)
1716 return 1.;
1717 else
1718 return cell->face(face_no)->measure();
1719 }
1720
1721 virtual void residual(VectorType &residual, const VectorType &solution_global, NumberType weight,
1722 const VectorType &solution_global_dot, NumberType weight_mass,
1723 const VectorType &variables = VectorType()) override
1724 {
1725 using CopyData = internal::CopyData_R<NumberType>;
1726 const auto &constraints = discretization.get_constraints();
1727
1728 // Find the EoM and extract whatever data is needed for the model, as the FEM assemblers do. Beyond
1729 // filling `extracted_data` this is what gives a model the chance to refresh whatever internal state its
1730 // flux depends on (interpolators, self-consistently solved anomalous dimensions, ...) before the fluxes
1731 // are evaluated. Models without extractors are unaffected.
1732 std::array<NumberType, Components::count_extractors()> __extracted_data{{}};
1733 if constexpr (Components::count_extractors() > 0)
1734 extract(__extracted_data, solution_global, variables, true, false, true);
1735
1736 Scratch scratch_data(quadrature);
1737 CopyData copy_data;
1738
1739 Timer timer;
1741 const auto &reconstruction_cache = residual_reconstruction_cache;
1742 const auto assembly_context = make_assembly_context_view(reconstruction_cache);
1744
1745 // Runs on the first residual assembly only, outside the mesh loop, so it costs
1746 // nothing on the hot path. The first assembly is also the most informative sample:
1747 // for an fRG flow it happens at k = Lambda, where a flux baseline is largest.
1750 probe_diffusion_flux_conditioning(reconstruction_cache, __extracted_data, variables);
1751 }
1752
1753 const auto cell_worker = [&](const Iterator &cell, Scratch &scratch_data, CopyData &copy_data) {
1754 const auto &cell_geometry = get_cell_topology(cell);
1755 constexpr uint n_dofs = n_components;
1756
1757 copy_data.reinit(cell, n_dofs);
1758
1759 fill_constant_quadrature_values(cell, solution_global, solution_global_dot, scratch_data);
1760 const auto &stencil = reconstruction_cache.cell_stencils[cell->active_cell_index()];
1761
1762 std::array<NumberType, n_components> mass{};
1763 std::array<NumberType, n_components> source{};
1764 for (size_t q_index = 0; q_index < cell_geometry.quadrature_points.size(); ++q_index) {
1765 const auto &x_q = cell_geometry.quadrature_points[q_index];
1766 const auto gradients = source_gradient<Reconstructor>(stencil, x_q);
1767 model.mass(mass, x_q, scratch_data.solution_values[q_index], scratch_data.solution_dot_values[q_index]);
1768 model.source(source, x_q,
1769 fv_tie(scratch_data.solution_values[q_index], gradients, __extracted_data, variables,
1770 cell_geometry.cell_width));
1771
1772 for (uint i = 0; i < n_dofs; ++i) {
1773 const auto component_i = local_component_of_dof[i];
1774 copy_data.cell_mass(i) +=
1775 weight_mass * cell_geometry.jxw[q_index] * mass[component_i]; // +phi_i(x_q) * mass(x_q, u_q)
1776 copy_data.cell_residual(i) +=
1777 cell_geometry.jxw[q_index] * weight * source[component_i]; // -phi_i(x_q) * source(x_q, u_q)
1778 }
1779 }
1780 };
1781
1782 const auto face_worker = [&](const Iterator &cell, const unsigned int &f,
1783 [[maybe_unused]] const unsigned int &sf, const Iterator &ncell,
1784 [[maybe_unused]] const unsigned int &nf,
1785 [[maybe_unused]] const unsigned int &nsf, [[maybe_unused]] Scratch &scratch_data,
1786 CopyData &copy_data) {
1787 [[maybe_unused]] const int q_face_index = 0; // only one quadrature point per face for FV (constant FE)
1788 const auto x_q = cell->face(f)->center();
1789 const auto n_face = face_normal_from_cell(cell, f);
1790 const auto JxW = face_jxw(cell, f);
1791 const uint n_face_dofs = 2 * n_components;
1792
1793 auto &copy_data_face = copy_data.next_face_data();
1794 copy_data_face.reinit(n_face_dofs);
1795
1796 const auto &reconstruction = get_cached_face_reconstruction(reconstruction_cache, cell, f);
1797 const auto &cell_stencil = reconstruction_cache.cell_stencils[cell->active_cell_index()];
1798 const auto &ncell_stencil = reconstruction_cache.cell_stencils[ncell->active_cell_index()];
1799 const auto &cell_data = cell_stencil.cell;
1800 const auto &ncell_data = ncell_stencil.cell;
1801
1802 const double width_minus = get_cell_topology(cell).cell_width;
1803 const double width_plus = get_cell_topology(ncell).cell_width;
1804
1805 const auto [F_plus, F_minus, a_half] = internal::compute_kt_flux_and_speeds<WaveSpeedStrategy>(
1806 reconstruction.u_plus, reconstruction.u_minus, reconstruction.face_grad_plus,
1807 reconstruction.face_grad_minus, x_q, width_plus, width_minus, __extracted_data, variables, model);
1808 const auto H = internal::compute_numerical_flux(F_plus, F_minus, a_half, reconstruction.u_plus,
1809 reconstruction.u_minus);
1810 const auto D = internal::compute_diffusion_flux(
1811 reconstruction.diffusion_u_minus, reconstruction.diffusion_u_plus, reconstruction.diffusion_grad_minus,
1812 reconstruction.diffusion_grad_plus, reconstruction.third_derivatives_minus,
1813 reconstruction.third_derivatives_plus, x_q, width_minus, width_plus, __extracted_data, variables,
1814 model);
1815
1816 // Sign convention: the face flux is (H + D)·n, i.e. the advection numerical
1817 // flux H (from flux()) and the diffusion flux D (from diffusion_flux())
1818 // are SUMMED. Both model methods therefore return the physical flux
1819 // with the same sign — exactly the conservation-law convention used by CG /
1820 // LLFFlux (which sums all contributions into one flux F). A diffusion flux
1821 // f_diff must be a DECREASING function of the gradient (∂f_diff/∂(∂u) < 0)
1822 // for forward diffusion, e.g. f_diff = -ν·∂u for the heat/viscous term.
1823 for (uint component_i = 0; component_i < n_components; ++component_i) {
1824 copy_data_face.joint_dof_indices[component_i] = cell_data.dof_indices[component_i];
1825 copy_data_face.joint_dof_indices[n_components + component_i] = ncell_data.dof_indices[component_i];
1826 const auto flux_contribution =
1827 weight * JxW * (scalar_product(H[component_i], n_face) + scalar_product(D[component_i], n_face));
1828 copy_data_face.cell_residual(component_i) += flux_contribution;
1829 copy_data_face.cell_residual(n_components + component_i) -= flux_contribution;
1830 }
1831 };
1832
1833 const auto boundary_worker = [&](const Iterator &cell, const unsigned int &face_no,
1834 [[maybe_unused]] Scratch &scratch_data, CopyData &copy_data) {
1835 const uint n_face_dofs = n_components;
1836
1837 auto &copy_data_face = copy_data.next_face_data();
1838 copy_data_face.reinit(n_face_dofs);
1839
1840 [[maybe_unused]] const int q_face_index = 0; // only one quadrature point per face for FV
1841 const auto x_q = cell->face(face_no)->center();
1842 const auto JxW = face_jxw(cell, face_no);
1843 const auto n_bnd = face_normal_from_cell(cell, face_no);
1844
1845 const auto &reconstruction = get_cached_face_reconstruction(reconstruction_cache, cell, face_no);
1846 const auto &cell_data = reconstruction_cache.cell_stencils[cell->active_cell_index()].cell;
1847
1848 const double width_minus = get_cell_topology(cell).cell_width;
1849 const double width_plus = width_minus;
1850
1851 const auto [F_plus, F_minus, a_half] = internal::compute_kt_flux_and_speeds<WaveSpeedStrategy>(
1852 reconstruction.u_plus, reconstruction.u_minus, reconstruction.face_grad_plus,
1853 reconstruction.face_grad_minus, x_q, width_plus, width_minus, __extracted_data, variables, model);
1854 const auto H = internal::compute_numerical_flux(F_plus, F_minus, a_half, reconstruction.u_plus,
1855 reconstruction.u_minus);
1856
1857 const auto D_bnd = internal::compute_diffusion_flux(
1858 reconstruction.diffusion_u_minus, reconstruction.diffusion_u_plus, reconstruction.diffusion_grad_minus,
1859 reconstruction.diffusion_grad_plus, reconstruction.third_derivatives_minus,
1860 reconstruction.third_derivatives_plus, x_q, width_minus, width_plus, __extracted_data, variables,
1861 model);
1862
1863 for (uint component_i = 0; component_i < n_components; ++component_i) {
1864 copy_data_face.joint_dof_indices[component_i] = cell_data.dof_indices[component_i];
1865 copy_data_face.cell_residual(component_i) +=
1866 weight * JxW * (scalar_product(H[component_i], n_bnd) + scalar_product(D_bnd[component_i], n_bnd));
1867 }
1868 };
1869
1870 const auto copier = [&](const CopyData &c) {
1871 constraints.distribute_local_to_global(c.cell_residual, c.local_dof_indices, residual);
1872 constraints.distribute_local_to_global(c.cell_mass, c.local_dof_indices, residual);
1873 for (unsigned int face_index = 0; face_index < c.active_face_count; ++face_index) {
1874 const auto &face_data = c.face_data[face_index];
1875 constraints.distribute_local_to_global(face_data.cell_residual, face_data.joint_dof_indices, residual);
1876 }
1877 };
1878
1879 MeshWorker::AssembleFlags flags = MeshWorker::assemble_own_cells | MeshWorker::assemble_boundary_faces |
1880 MeshWorker::assemble_own_interior_faces_once |
1881 MeshWorker::assemble_ghost_faces_once;
1882
1883 // map() is collective and each rank visits only its own cells; see NoMapsHere.
1884 const NoMapsHere no_maps_during_assembly;
1885 const auto schedule = schedule_for(assembly_cost::momentum_integral);
1886 MeshWorker::mesh_loop(locally_owned_cells(dof_handler), cell_worker, copier, scratch_data, copy_data, flags,
1887 boundary_worker, face_worker, schedule.queue_length, schedule.chunk_size);
1888 // Resolve contributions this rank made to rows it does not own. A partition-boundary
1889 // face is assembled by exactly one of its two neighbours (mesh_loop hands it to the
1890 // smaller subdomain id), and that rank writes BOTH sides -- so the other side's rows
1891 // arrive here. A no-op for the serial types.
1892 residual.compress(dealii::VectorOperation::add);
1893 timings_residual.push_back(timer.wall_time());
1894 }
1895
1896 virtual void jacobian_mass(SparseMatrixType &jacobian, const VectorType &solution_global,
1897 const VectorType &solution_global_dot, NumberType alpha = 1.,
1898 NumberType beta = 1.) override
1899 {
1900 using Iterator = typename DoFHandler<dim>::active_cell_iterator;
1902 const auto &constraints = discretization.get_constraints();
1903
1904 const auto cell_worker = [&](const Iterator &cell, Scratch &scratch_data, CopyData &copy_data) {
1905 const auto &cell_geometry = get_cell_topology(cell);
1906 constexpr uint n_dofs = n_components;
1907
1908 copy_data.reinit(cell, n_dofs, Components::count_extractors());
1909
1910 fill_constant_quadrature_values(cell, solution_global, solution_global_dot, scratch_data);
1911
1914 for (size_t q_index = 0; q_index < cell_geometry.quadrature_points.size(); ++q_index) {
1915 const auto &x_q = cell_geometry.quadrature_points[q_index];
1916 model.template jacobian_mass<0>(j_mass, x_q, scratch_data.solution_values[q_index],
1917 scratch_data.solution_dot_values[q_index]);
1918 model.template jacobian_mass<1>(j_mass_dot, x_q, scratch_data.solution_values[q_index],
1919 scratch_data.solution_dot_values[q_index]);
1920
1921 for (uint i = 0; i < n_dofs; ++i) {
1922 const auto component_i = local_component_of_dof[i];
1923 for (uint j = 0; j < n_dofs; ++j) {
1924 const auto component_j = local_component_of_dof[j];
1925 copy_data.cell_jacobian(i, j) +=
1926 cell_geometry.jxw[q_index] *
1927 (alpha * j_mass_dot(component_i, component_j) + beta * j_mass(component_i, component_j));
1928 }
1929 }
1930 }
1931 };
1932 const auto copier = [&](const CopyData &c) {
1933 constraints.distribute_local_to_global(c.cell_jacobian, c.local_dof_indices, jacobian);
1934 };
1935
1936 Scratch scratch_data(quadrature);
1937 CopyData copy_data;
1938 MeshWorker::AssembleFlags flags = MeshWorker::assemble_own_cells;
1939
1940 Timer timer;
1941 // map() is collective and each rank visits only its own cells; see NoMapsHere.
1942 const NoMapsHere no_maps_during_assembly;
1943 const auto schedule = schedule_for(assembly_cost::local_fe);
1944 MeshWorker::mesh_loop(locally_owned_cells(dof_handler), cell_worker, copier, scratch_data, copy_data, flags,
1945 nullptr, nullptr, schedule.queue_length, schedule.chunk_size);
1946 // Resolve contributions this rank made to rows it does not own. A partition-boundary
1947 // face is assembled by exactly one of its two neighbours (mesh_loop hands it to the
1948 // smaller subdomain id), and that rank writes BOTH sides -- so the other side's rows
1949 // arrive here. A no-op for the serial types.
1950 jacobian.compress(dealii::VectorOperation::add);
1951 timings_jacobian.push_back(timer.wall_time());
1952 }
1953
1954 virtual void jacobian(SparseMatrixType &jacobian, const VectorType &solution_global, NumberType weight,
1955 const VectorType &solution_global_dot, NumberType alpha, NumberType beta,
1956 const VectorType &variables = VectorType()) override
1957 {
1958 using Iterator = typename DoFHandler<dim>::active_cell_iterator;
1960 const auto &constraints = discretization.get_constraints();
1961
1962 // See residual(): keep the model's extractor-driven state consistent with the point the jacobian is
1963 // linearised about. The extractor jacobian contribution itself is not assembled here (the FV
1964 // extractor_cell_jacobian blocks remain unused), so extractors are treated as frozen w.r.t. the FE
1965 // solution within a Newton step -- fine for the IDA/explicit split where Variables are stepped
1966 // explicitly, but it is why jacobian_variables below is still a no-op.
1967 std::array<NumberType, Components::count_extractors()> __extracted_data{{}};
1968 if constexpr (Components::count_extractors() > 0)
1969 extract(__extracted_data, solution_global, variables, true, false, true);
1970 Timer timer;
1972 const auto &reconstruction_cache = jacobian_reconstruction_cache;
1973 const auto assembly_context = make_assembly_context_view(reconstruction_cache);
1975
1976 const auto cell_worker = [&](const Iterator &cell, Scratch &scratch_data, CopyData &copy_data) {
1977 const auto &cell_geometry = get_cell_topology(cell);
1978 constexpr uint n_dofs = n_components;
1979
1980 copy_data.reinit(cell, n_dofs, Components::count_extractors());
1981
1982 fill_constant_quadrature_values(cell, solution_global, solution_global_dot, scratch_data);
1983 const auto &stencil = reconstruction_cache.cell_stencils[cell->active_cell_index()];
1984
1985 // The source's gradient dependence makes it nonlocal -- it reaches the whole 2*dim stencil
1986 // through the reconstruction -- so that part cannot live in the square, cell-local
1987 // cell_jacobian. It goes into a rectangular block, exactly as the face workers do.
1988 auto &copy_data_source = copy_data.next_face_data();
1989 copy_data_source.reinit(cell_geometry.source_jacobian_dependencies.to_dofs,
1990 cell_geometry.source_jacobian_dependencies.from_dofs);
1991 const uint n_source_from = size(copy_data_source.from_dofs);
1992 auto &source_grad_deriv = scratch_data.source_gradient_derivatives;
1993 source_grad_deriv.resize(n_source_from);
1994
1999 for (size_t q_index = 0; q_index < cell_geometry.quadrature_points.size(); ++q_index) {
2000 const auto &x_q = cell_geometry.quadrature_points[q_index];
2001 const auto gradients = source_gradient<JacobianReconstructor>(stencil, x_q);
2002 const auto source_tie = fv_tie(scratch_data.solution_values[q_index], gradients, __extracted_data,
2003 variables, cell_geometry.cell_width);
2004 model.template jacobian_mass<0>(j_mass, x_q, scratch_data.solution_values[q_index],
2005 scratch_data.solution_dot_values[q_index]);
2006 model.template jacobian_mass<1>(j_mass_dot, x_q, scratch_data.solution_values[q_index],
2007 scratch_data.solution_dot_values[q_index]);
2008 model.template jacobian_source<0, 0>(j_source, x_q, source_tie);
2009 // No jacobian_source_extr counterpart: KT never assembles extractor_cell_jacobian and
2010 // jacobian_variables is a no-op, so extractors and variables are frozen within a Newton step.
2011 model.template jacobian_source_grad<1>(j_grad_source, x_q, source_tie);
2012
2013 for (uint i = 0; i < n_dofs; ++i) {
2014 const auto component_i = local_component_of_dof[i];
2015 for (uint j = 0; j < n_dofs; ++j) {
2016 const auto component_j = local_component_of_dof[j];
2017 copy_data.cell_jacobian(i, j) +=
2018 weight * cell_geometry.jxw[q_index] * j_source(component_i, component_j); // -phi_i * jsource
2019 copy_data.cell_mass_jacobian(i, j) +=
2020 cell_geometry.jxw[q_index] *
2021 (alpha * j_mass_dot(component_i, component_j) + beta * j_mass(component_i, component_j));
2022 }
2023 }
2024
2025 // d(source)/d(u_j) via the gradient: seed one stencil dof at a time, exactly as the face
2026 // workers do for the flux, and chain d(grad)/d(u_j) into the model's dsource/dgrad.
2027 for (uint j = 0; j < n_source_from; ++j) {
2028 const auto stencil_tagged =
2029 tag_cell_stencil_dofs_from_cache(cell, stencil, solution_global, copy_data_source.from_dofs[j]);
2030 source_grad_deriv[j] =
2031 JacobianReconstructor::template compute_gradient_at_point_derivative<n_components>(
2032 stencil.cell.x, x_q, stencil_tagged.cell.u, stencil.neighbors.x, stencil_tagged.neighbors.u);
2033 }
2034 for (uint i = 0; i < n_dofs; ++i) {
2035 const auto component_i = local_component_of_dof[i];
2036 for (uint j = 0; j < n_source_from; ++j) {
2037 NumberType contribution{};
2038 for (uint c = 0; c < n_components; ++c)
2039 for (uint d = 0; d < dim; ++d)
2040 contribution += j_grad_source(component_i, c)[d] * source_grad_deriv[j][c][d];
2041 copy_data_source.cell_jacobian(i, j) += weight * cell_geometry.jxw[q_index] * contribution;
2042 }
2043 }
2044 }
2045 };
2046 const auto face_worker = [&](const Iterator &cell, const unsigned int &f,
2047 [[maybe_unused]] const unsigned int &sf, const Iterator &ncell,
2048 [[maybe_unused]] const unsigned int &nf,
2049 [[maybe_unused]] const unsigned int &nsf, Scratch &scratch_data,
2050 CopyData &copy_data) {
2051 [[maybe_unused]] const int q_face_index = 0;
2052 const auto x_q = cell->face(f)->center();
2053 const auto JxW = face_jxw(cell, f);
2054 const auto n_face = face_normal_from_cell(cell, f);
2055
2056 const auto &reconstruction = get_cached_face_reconstruction(reconstruction_cache, cell, f);
2057 const auto &cell_stencil = reconstruction_cache.cell_stencils[cell->active_cell_index()];
2058 const auto &ncell_stencil = reconstruction_cache.cell_stencils[ncell->active_cell_index()];
2059 const auto &cell_neighbors = cell_stencil.neighbors;
2060 const auto &ncell_neighbors = ncell_stencil.neighbors;
2061 const auto &cell_data = cell_stencil.cell;
2062 const auto &ncell_data = ncell_stencil.cell;
2063
2064 auto &copy_data_face = copy_data.next_face_data();
2065 const auto &face_dependencies = get_cell_topology(cell).face_jacobian_dependencies[f];
2066 copy_data_face.reinit(face_dependencies.to_dofs, face_dependencies.from_dofs);
2067
2068 // Precompute reconstructed state and face-gradient derivatives for each dependency dof.
2069 const uint n_from = size(copy_data_face.from_dofs);
2070 for (auto &derivatives : scratch_data.reconstructed_derivatives) {
2071 if (derivatives.capacity() < n_from) derivatives.reserve(n_from);
2072 derivatives.resize(n_from);
2073 }
2074 auto &reconstructed_deriv = scratch_data.reconstructed_derivatives;
2075 auto &diffusion_deriv = scratch_data.diffusion_derivatives;
2076 for (auto &derivatives : diffusion_deriv) {
2077 if (derivatives.capacity() < n_from) derivatives.reserve(n_from);
2078 derivatives.resize(n_from);
2079 }
2080
2081 for (uint j = 0; j < size(copy_data_face.from_dofs); ++j) {
2082 const auto dof_j = copy_data_face.from_dofs[j];
2083 const auto cell_stencil_tagged =
2084 tag_cell_stencil_dofs_from_cache(cell, cell_stencil, solution_global, dof_j);
2085 const auto ncell_stencil_tagged =
2086 tag_cell_stencil_dofs_from_cache(ncell, ncell_stencil, solution_global, dof_j);
2087
2088 // face_no=0: d(u⁻)/d(u_j) — reconstruction from cell side
2089 {
2090 reconstructed_deriv[0][j].u =
2092 cell_stencil_tagged.cell.u, cell_data.x, x_q, cell_neighbors.x,
2093 cell_stencil_tagged.neighbors.u);
2094 reconstructed_deriv[0][j].grad =
2095 JacobianReconstructor::template compute_gradient_at_point_derivative<n_components>(
2096 cell_data.x, x_q, cell_stencil_tagged.cell.u, cell_neighbors.x,
2097 cell_stencil_tagged.neighbors.u);
2098 }
2099
2100 // face_no=1: d(u⁺)/d(u_j) — reconstruction from neighbor side
2101 {
2102 reconstructed_deriv[1][j].u =
2104 ncell_stencil_tagged.cell.u, ncell_data.x, x_q, ncell_neighbors.x,
2105 ncell_stencil_tagged.neighbors.u);
2106 reconstructed_deriv[1][j].grad =
2107 JacobianReconstructor::template compute_gradient_at_point_derivative<n_components>(
2108 ncell_data.x, x_q, ncell_stencil_tagged.cell.u, ncell_neighbors.x,
2109 ncell_stencil_tagged.neighbors.u);
2110 }
2111
2112 if constexpr (dim == 1) {
2113 const auto third_derivative_stencil_ad =
2114 internal::make_interior_third_derivative_stencil(cell_stencil_tagged, ncell_stencil_tagged, x_q);
2115 const auto third_derivatives =
2116 JacobianReconstructor::template compute_third_derivatives_at_face_derivative<n_components>(
2117 third_derivative_stencil_ad.x, third_derivative_stencil_ad.u);
2118 reconstructed_deriv[0][j].third_derivatives = third_derivatives;
2119 reconstructed_deriv[1][j].third_derivatives = third_derivatives;
2120 }
2121
2122 const auto diffusion_face_ad =
2123 internal::compute_diffusion_face_state(cell_stencil_tagged, ncell_stencil_tagged);
2124 const auto diffusion_face_derivatives =
2126 diffusion_deriv[0][j] = diffusion_face_derivatives[0];
2127 diffusion_deriv[1][j] = diffusion_face_derivatives[1];
2128 }
2129
2130 const double width_minus = get_cell_topology(cell).cell_width;
2131 const double width_plus = get_cell_topology(ncell).cell_width;
2132
2133 const auto j_numflux =
2135 reconstruction.u_plus, reconstruction.u_minus, reconstruction.face_grad_plus,
2136 reconstruction.face_grad_minus, x_q, width_plus, width_minus, __extracted_data, variables, model);
2138 reconstruction.diffusion_u_minus, reconstruction.diffusion_u_plus, reconstruction.diffusion_grad_minus,
2139 reconstruction.diffusion_grad_plus, reconstruction.third_derivatives_minus,
2140 reconstruction.third_derivatives_plus, x_q, width_minus, width_plus, __extracted_data, variables,
2141 model);
2142
2143 for (uint i = 0; i < size(copy_data_face.to_dofs); ++i) {
2144 const bool cell_side_i = i < n_components;
2145 const auto component_i = cell_side_i ? i : i - n_components;
2146 const auto jump_i = cell_side_i ? NumberType(1.) : NumberType(-1.);
2147 for (uint j = 0; j < size(copy_data_face.from_dofs); ++j) {
2148 NumberType diffusion_contribution{};
2149 for (size_t face_no = 0; face_no < 2; ++face_no) {
2150 NumberType advection_contribution{};
2151 for (size_t c = 0; c < n_components; ++c) {
2152 advection_contribution += scalar_product(j_numflux.u[face_no](component_i, c), n_face) *
2153 reconstructed_deriv[face_no][j].u[c];
2154 for (size_t d_in = 0; d_in < dim; ++d_in)
2155 for (size_t d_out = 0; d_out < dim; ++d_out)
2156 advection_contribution += j_numflux.grad[face_no](component_i, c)[d_out][d_in] * n_face[d_out] *
2157 reconstructed_deriv[face_no][j].grad[c][d_in];
2158 }
2159 copy_data_face.cell_jacobian(i, j) += weight * JxW * jump_i * advection_contribution;
2160 }
2161
2162 for (size_t face_no = 0; face_no < 2; ++face_no)
2163 for (size_t c = 0; c < n_components; ++c) {
2164 diffusion_contribution += scalar_product(j_diffusion.u[face_no](component_i, c), n_face) *
2165 diffusion_deriv[face_no][j].u[c];
2166 for (size_t d_in = 0; d_in < dim; ++d_in)
2167 for (size_t d_out = 0; d_out < dim; ++d_out)
2168 diffusion_contribution += j_diffusion.grad[face_no](component_i, c)[d_out][d_in] *
2169 n_face[d_out] * diffusion_deriv[face_no][j].grad[c][d_in];
2170 for (size_t d0 = 0; d0 < dim; ++d0)
2171 for (size_t d1 = 0; d1 < dim; ++d1)
2172 for (size_t d2 = 0; d2 < dim; ++d2)
2173 for (size_t d_out = 0; d_out < dim; ++d_out)
2174 diffusion_contribution +=
2175 j_diffusion.third_derivatives[face_no](component_i, c)[d_out][d0][d1][d2] *
2176 n_face[d_out] * reconstructed_deriv[face_no][j].third_derivatives[c][d0][d1][d2];
2177 }
2178
2179 // The residual uses [[phi_i]] * ((H + D) · n). The diffusion
2180 // path has two corrected side gradients, separate from the two KT
2181 // advective traces.
2182 copy_data_face.cell_jacobian(i, j) += weight * JxW * jump_i * diffusion_contribution;
2183 }
2184 }
2185 };
2186
2187 const auto boundary_worker = [&](const Iterator &cell, const unsigned int &face_no, Scratch &scratch_data,
2188 CopyData &copy_data) {
2189 [[maybe_unused]] const int q_face_index = 0;
2190 const auto x_q = cell->face(face_no)->center();
2191 const auto JxW = face_jxw(cell, face_no);
2192 const auto n_face = face_normal_from_cell(cell, face_no);
2193
2194 auto &copy_data_face = copy_data.next_face_data();
2195 const auto &face_dependencies = get_cell_topology(cell).face_jacobian_dependencies[face_no];
2196 copy_data_face.reinit(face_dependencies.to_dofs, face_dependencies.from_dofs);
2197
2198 const auto boundary_stencil =
2200 const auto &cell_stencil = reconstruction_cache.cell_stencils[cell->active_cell_index()];
2201 const auto &reconstruction = get_cached_face_reconstruction(reconstruction_cache, cell, face_no);
2202
2203 // Precompute reconstructed state and face-gradient derivatives for each dependency dof.
2204 const uint n_from = size(copy_data_face.from_dofs);
2205 for (auto &derivatives : scratch_data.reconstructed_derivatives) {
2206 if (derivatives.capacity() < n_from) derivatives.reserve(n_from);
2207 derivatives.resize(n_from);
2208 }
2209 auto &reconstructed_deriv = scratch_data.reconstructed_derivatives;
2210 auto &diffusion_deriv = scratch_data.diffusion_derivatives;
2211 for (auto &derivatives : diffusion_deriv) {
2212 if (derivatives.capacity() < n_from) derivatives.reserve(n_from);
2213 derivatives.resize(n_from);
2214 }
2215
2216 for (uint j = 0; j < size(copy_data_face.from_dofs); ++j) {
2217 const auto dof_j = copy_data_face.from_dofs[j];
2218 const auto boundary_stencil_ad =
2220 dof_j);
2221 const auto cell_stencil_ad = tag_cell_stencil_dofs_from_cache(cell, cell_stencil, solution_global, dof_j);
2222 const auto [physical_stencil_ad, ghost_stencil_ad] =
2223 internal::make_model_boundary_reconstruction_side_stencils(boundary_stencil_ad, cell_stencil_ad, x_q,
2224 model);
2225 reconstructed_deriv[0][j].u =
2227 physical_stencil_ad.cell.u, physical_stencil_ad.cell.x, x_q, physical_stencil_ad.neighbors.x,
2228 physical_stencil_ad.neighbors.u);
2229 reconstructed_deriv[1][j].u =
2231 ghost_stencil_ad.cell.u, ghost_stencil_ad.cell.x, x_q, ghost_stencil_ad.neighbors.x,
2232 ghost_stencil_ad.neighbors.u);
2233 reconstructed_deriv[0][j].grad =
2234 JacobianReconstructor::template compute_gradient_at_point_derivative<n_components>(
2235 physical_stencil_ad.cell.x, x_q, physical_stencil_ad.cell.u, physical_stencil_ad.neighbors.x,
2236 physical_stencil_ad.neighbors.u);
2237 reconstructed_deriv[1][j].grad =
2238 JacobianReconstructor::template compute_gradient_at_point_derivative<n_components>(
2239 ghost_stencil_ad.cell.x, x_q, ghost_stencil_ad.cell.u, ghost_stencil_ad.neighbors.x,
2240 ghost_stencil_ad.neighbors.u);
2241
2242 if constexpr (dim == 1) {
2243 auto third_derivative_boundary_stencil_ad = boundary_stencil_ad.primary;
2244 internal::apply_boundary_reconstruction_stencil(third_derivative_boundary_stencil_ad, cell_stencil_ad,
2245 model);
2246 const auto third_derivative_stencil_ad =
2247 internal::make_boundary_third_derivative_stencil(third_derivative_boundary_stencil_ad);
2248 const auto third_derivatives =
2249 JacobianReconstructor::template compute_third_derivatives_at_face_derivative<n_components>(
2250 third_derivative_stencil_ad.x, third_derivative_stencil_ad.u);
2251 reconstructed_deriv[0][j].third_derivatives = third_derivatives;
2252 reconstructed_deriv[1][j].third_derivatives = third_derivatives;
2253 }
2254
2255 const auto diffusion_face_ad =
2256 internal::compute_diffusion_face_state(physical_stencil_ad, ghost_stencil_ad);
2257 const auto diffusion_face_derivatives =
2259 diffusion_deriv[0][j] = diffusion_face_derivatives[0];
2260 diffusion_deriv[1][j] = diffusion_face_derivatives[1];
2261 }
2262
2263 // Compute numerical flux Jacobian
2264 const double width_minus = get_cell_topology(cell).cell_width;
2265 const double width_plus = width_minus;
2266
2267 const auto j_numflux =
2269 reconstruction.u_plus, reconstruction.u_minus, reconstruction.face_grad_plus,
2270 reconstruction.face_grad_minus, x_q, width_plus, width_minus, __extracted_data, variables, model);
2272 reconstruction.diffusion_u_minus, reconstruction.diffusion_u_plus, reconstruction.diffusion_grad_minus,
2273 reconstruction.diffusion_grad_plus, reconstruction.third_derivatives_minus,
2274 reconstruction.third_derivatives_plus, x_q, width_minus, width_plus, __extracted_data, variables,
2275 model);
2276
2277 // Chain-rule assembly (same pattern as interior face_worker)
2278 for (uint i = 0; i < size(copy_data_face.to_dofs); ++i) {
2279 const auto component_i = i;
2280 for (uint j = 0; j < size(copy_data_face.from_dofs); ++j) {
2281 NumberType diffusion_contribution{};
2282 for (size_t face_no = 0; face_no < 2; ++face_no) {
2283 NumberType advection_contribution{};
2284 for (size_t c = 0; c < n_components; ++c) {
2285 advection_contribution += scalar_product(j_numflux.u[face_no](component_i, c), n_face) *
2286 reconstructed_deriv[face_no][j].u[c];
2287 for (size_t d_in = 0; d_in < dim; ++d_in)
2288 for (size_t d_out = 0; d_out < dim; ++d_out)
2289 advection_contribution += j_numflux.grad[face_no](component_i, c)[d_out][d_in] * n_face[d_out] *
2290 reconstructed_deriv[face_no][j].grad[c][d_in];
2291 }
2292 copy_data_face.cell_jacobian(i, j) += weight * JxW * advection_contribution;
2293 }
2294
2295 for (size_t face_no = 0; face_no < 2; ++face_no)
2296 for (size_t c = 0; c < n_components; ++c) {
2297 diffusion_contribution += scalar_product(j_diffusion.u[face_no](component_i, c), n_face) *
2298 diffusion_deriv[face_no][j].u[c];
2299 for (size_t d_in = 0; d_in < dim; ++d_in)
2300 for (size_t d_out = 0; d_out < dim; ++d_out)
2301 diffusion_contribution += j_diffusion.grad[face_no](component_i, c)[d_out][d_in] *
2302 n_face[d_out] * diffusion_deriv[face_no][j].grad[c][d_in];
2303 for (size_t d0 = 0; d0 < dim; ++d0)
2304 for (size_t d1 = 0; d1 < dim; ++d1)
2305 for (size_t d2 = 0; d2 < dim; ++d2)
2306 for (size_t d_out = 0; d_out < dim; ++d_out)
2307 diffusion_contribution +=
2308 j_diffusion.third_derivatives[face_no](component_i, c)[d_out][d0][d1][d2] *
2309 n_face[d_out] * reconstructed_deriv[face_no][j].third_derivatives[c][d0][d1][d2];
2310 }
2311
2312 // Boundary diffusion uses the same ghost-stencil side labels as the
2313 // advective boundary reconstruction, but it is differentiated as one
2314 // pair of corrected face-gradient operators.
2315 copy_data_face.cell_jacobian(i, j) += weight * JxW * diffusion_contribution;
2316 }
2317 }
2318 };
2319
2320 const auto copier = [&](const CopyData &c) {
2321 constraints.distribute_local_to_global(c.cell_jacobian, c.local_dof_indices, jacobian);
2322 constraints.distribute_local_to_global(c.cell_mass_jacobian, c.local_dof_indices, jacobian);
2323 for (unsigned int face_index = 0; face_index < c.active_face_count; ++face_index) {
2324 const auto &face_data = c.face_data[face_index];
2325 constraints.distribute_local_to_global(face_data.cell_jacobian, face_data.to_dofs, face_data.from_dofs,
2326 jacobian);
2327 }
2328 };
2329
2330 Scratch scratch_data(quadrature);
2331 CopyData copy_data;
2332 MeshWorker::AssembleFlags flags = MeshWorker::assemble_own_cells | MeshWorker::assemble_boundary_faces |
2333 MeshWorker::assemble_own_interior_faces_once |
2334 MeshWorker::assemble_ghost_faces_once;
2335
2336 // map() is collective and each rank visits only its own cells; see NoMapsHere.
2337 const NoMapsHere no_maps_during_assembly;
2338 const auto schedule = schedule_for(assembly_cost::momentum_integral);
2339 MeshWorker::mesh_loop(locally_owned_cells(dof_handler), cell_worker, copier, scratch_data, copy_data, flags,
2340 boundary_worker, face_worker, schedule.queue_length, schedule.chunk_size);
2341 // Resolve contributions this rank made to rows it does not own. A partition-boundary
2342 // face is assembled by exactly one of its two neighbours (mesh_loop hands it to the
2343 // smaller subdomain id), and that rank writes BOTH sides -- so the other side's rows
2344 // arrive here. A no-op for the serial types.
2345 jacobian.compress(dealii::VectorOperation::add);
2346 timings_jacobian.push_back(timer.wall_time());
2347 }
2348
2349 virtual void refinement_indicator([[maybe_unused]] Vector<double> &indicator,
2350 [[maybe_unused]] const VectorType &solution_global)
2351 {
2352 }
2353
2354 template <typename DoFContainer>
2355 static void append_dofs(std::vector<types::global_dof_index> &target, const DoFContainer &source)
2356 {
2357 target.insert(target.end(), source.begin(), source.end());
2358 }
2359
2360 template <typename DoFContainer>
2361 static void append_valid_dofs(std::vector<types::global_dof_index> &target, const DoFContainer &source)
2362 {
2363 for (const auto dof : source)
2364 if (dof != numbers::invalid_dof_index) target.push_back(dof);
2365 }
2366
2367 static void sort_unique_dofs(std::vector<types::global_dof_index> &dofs)
2368 {
2369 std::sort(dofs.begin(), dofs.end());
2370 dofs.erase(std::unique(dofs.begin(), dofs.end()), dofs.end());
2371 }
2372
2373 void append_reconstruction_neighbor_dofs(std::vector<types::global_dof_index> &from_dofs,
2374 const Iterator &root_cell) const
2375 {
2376 std::vector<types::global_dof_index> neighbor_dof_indices(fe.dofs_per_cell);
2377
2378 const auto append_recursive = [&](const auto &self, const Iterator &cell, const unsigned int depth) -> void {
2379 for (const auto face_index : cell->face_indices()) {
2380 if (is_physical_boundary_face(cell, face_index)) continue;
2381
2382 const auto neighbor = face_neighbor(cell, face_index);
2383 neighbor->get_dof_indices(neighbor_dof_indices);
2384 from_dofs.insert(from_dofs.end(), neighbor_dof_indices.begin(), neighbor_dof_indices.end());
2385
2386 if (depth > 1) self(self, neighbor, depth - 1);
2387 }
2388 };
2389
2390 append_recursive(append_recursive, root_cell, 2);
2391 }
2392
2393 void append_boundary_reconstruction_dofs(std::vector<types::global_dof_index> &from_dofs,
2394 const unsigned int cell_index, const unsigned int face_index) const
2395 {
2396 const auto append_boundary_stencil_dofs =
2398 for (const auto &dofs : topology.dof_indices)
2399 append_valid_dofs(from_dofs, dofs);
2400 };
2401
2402 const auto &topology = cell_topology_cache[cell_index].boundary_stencils[face_index];
2403 append_boundary_stencil_dofs(topology.primary);
2404 for (size_t face = 0; face < topology.tangential_ghost_neighbor_valid.size(); ++face) {
2405 if (topology.tangential_ghost_neighbor_valid[face])
2406 append_boundary_stencil_dofs(topology.tangential_ghost_neighbors[face]);
2407 if (topology.corner_tangential_stencil_valid[face]) {
2408 for (const auto &corner_stencil : topology.corner_tangential_stencils[face])
2409 append_boundary_stencil_dofs(corner_stencil);
2410 }
2411 }
2412 }
2413
2414 void build_face_jacobian_dependency_cache(const Iterator &cell, const unsigned int face_index,
2415 FaceJacobianDependencyCacheEntry &dependencies) const
2416 {
2417 const auto &cell_topology = cell_topology_cache[cell->active_cell_index()].stencil;
2418
2419 dependencies.to_dofs.clear();
2420 append_dofs(dependencies.to_dofs, cell_topology.cell.dof_indices);
2421 if (!is_physical_boundary_face(cell, face_index))
2422 append_dofs(dependencies.to_dofs, cell_topology.neighbors.dof_indices[face_index]);
2423
2424 dependencies.from_dofs = dependencies.to_dofs;
2425 if (is_physical_boundary_face(cell, face_index))
2426 append_boundary_reconstruction_dofs(dependencies.from_dofs, cell->active_cell_index(), face_index);
2427 if constexpr (JacobianReconstructor::jacobian_stencil_radius > 1) {
2429 if (!is_physical_boundary_face(cell, face_index))
2430 append_reconstruction_neighbor_dofs(dependencies.from_dofs, face_neighbor(cell, face_index));
2431 }
2432 sort_unique_dofs(dependencies.from_dofs);
2433 }
2434
2443 FaceJacobianDependencyCacheEntry &dependencies) const
2444 {
2445 const auto &cell_topology = cell_topology_cache[cell->active_cell_index()].stencil;
2446
2447 dependencies.to_dofs.clear();
2448 append_dofs(dependencies.to_dofs, cell_topology.cell.dof_indices);
2449
2450 dependencies.from_dofs = dependencies.to_dofs;
2451 for (const auto face_index : cell->face_indices()) {
2452 if (is_physical_boundary_face(cell, face_index))
2453 append_boundary_reconstruction_dofs(dependencies.from_dofs, cell->active_cell_index(), face_index);
2454 else
2455 append_valid_dofs(dependencies.from_dofs, cell_topology.neighbors.dof_indices[face_index]);
2456 }
2457 sort_unique_dofs(dependencies.from_dofs);
2458 }
2459
2461 const DoFHandler<dim> &to_dofh, const DoFHandler<dim> &from_dofh, const int stencil = 2,
2462 [[maybe_unused]] bool add_extractor_dofs = false) const
2463 {
2464 const auto &triangulation = discretization.get_triangulation();
2465
2466 DynamicSparsityPattern dsp(discretization.get_locally_relevant_dofs());
2467
2468 const auto to_dofs_per_cell = to_dofh.get_fe().dofs_per_cell;
2469 const auto from_dofs_per_cell = from_dofh.get_fe().dofs_per_cell;
2470
2471 for (const auto &t_cell : triangulation.active_cell_iterators()) {
2472 std::vector<types::global_dof_index> to_dofs(to_dofs_per_cell);
2473 std::vector<types::global_dof_index> from_dofs;
2474 from_dofs.reserve(from_dofs_per_cell +
2475 stencil * from_dofs_per_cell); // reserve enough space for the cell itself + neighbors
2476 const auto to_cell = typename DoFHandler<dim>::active_cell_iterator(
2477 &to_dofh.get_triangulation(), t_cell->level(), t_cell->index(), &to_dofh);
2478 const auto from_cell = typename DoFHandler<dim>::active_cell_iterator(
2479 &from_dofh.get_triangulation(), t_cell->level(), t_cell->index(), &from_dofh);
2480 to_cell->get_dof_indices(to_dofs);
2481 from_cell->get_dof_indices(from_dofs);
2482
2483 std::function<void(decltype(from_cell) &, const int)> add_all_neighbor_dofs =
2484 [&](const auto &from_cell, const int stencil_level = 1) {
2485 for (const auto face_no : from_cell->face_indices()) {
2486 const auto face = from_cell->face(face_no);
2487 if (!is_physical_boundary_face(from_cell, face_no)) {
2488 auto neighbor_cell = face_neighbor(from_cell, face_no);
2489
2490 if (neighbor_cell->has_children()) {
2491 throw std::runtime_error("AMR is not yet supported in the Kurganov-Tadmor assembler.");
2492 }
2493
2494 std::vector<types::global_dof_index> tmp(from_dofs_per_cell);
2495 neighbor_cell->get_dof_indices(tmp);
2496
2497 from_dofs.insert(std::end(from_dofs), std::begin(tmp), std::end(tmp)); // Let's wait for C++23 :(
2498
2499 if (stencil_level < stencil) add_all_neighbor_dofs(neighbor_cell, stencil_level + 1);
2500 }
2501 }
2502 };
2503
2504 add_all_neighbor_dofs(from_cell, 1);
2505
2506 for (const auto i : to_dofs)
2507 for (const auto j : from_dofs)
2508 dsp.add(i, j);
2509 }
2510
2511 // if (add_extractor_dofs)
2512 // throw std::runtime_error("Extractor dofs are not yet supported in the Kurganov-Tadmor assembler.");
2513 finalize_la_sparsity<SparseMatrixType>(dsp, sparsity_pattern, discretization.get_locally_owned_dofs(),
2514 discretization.get_locally_relevant_dofs(),
2515 discretization.get_communicator());
2516 }
2517
2519 {
2520 DynamicSparsityPattern dsp(discretization.get_locally_relevant_dofs());
2521 for (const auto &cell : dof_handler.active_cell_iterators()) {
2522 const auto &topology = get_cell_topology(cell);
2523 const auto &cell_dofs = topology.stencil.cell.dof_indices;
2524 for (const auto row : cell_dofs)
2525 for (const auto column : cell_dofs)
2526 dsp.add(row, column);
2527
2528 const auto add_block = [&](const FaceJacobianDependencyCacheEntry &dependencies) {
2529 for (const auto row : dependencies.to_dofs)
2530 for (const auto column : dependencies.from_dofs)
2531 if (row != numbers::invalid_dof_index && column != numbers::invalid_dof_index) dsp.add(row, column);
2532 };
2533 for (const auto face_index : cell->face_indices())
2534 add_block(topology.face_jacobian_dependencies[face_index]);
2535 // Subsumed by the face blocks in practice, but the source jacobian must not depend on that.
2536 add_block(topology.source_jacobian_dependencies);
2537 }
2538 finalize_la_sparsity<SparseMatrixType>(dsp, sparsity_pattern, discretization.get_locally_owned_dofs(),
2539 discretization.get_locally_relevant_dofs(),
2540 discretization.get_communicator());
2541 }
2542
2544 const Iterator &cell, const unsigned int boundary_face_no,
2545 std::vector<types::global_dof_index> &dof_indices) const
2546 {
2547 using namespace def::BoundaryStencilIndex;
2548 boundary_topology.lower_boundary = boundary_face_no % 2 == 0;
2549 boundary_topology.cell_face = boundary_face_no;
2550 boundary_topology.face_center = cell->face(boundary_face_no)->center();
2551 boundary_topology.ghost_center = boundary_topology.lower_boundary ? lower_inner : upper_inner;
2552 boundary_topology.ghost_left = boundary_topology.lower_boundary ? lower_outer : physical_cell;
2553 boundary_topology.ghost_right = boundary_topology.lower_boundary ? physical_cell : upper_outer;
2554 for (auto &dofs : boundary_topology.dof_indices)
2555 dofs.fill(numbers::invalid_dof_index);
2556
2557 cell->get_dof_indices(dof_indices);
2558 boundary_topology.x[physical_cell] = cell->center();
2559 for (uint i = 0; i < n_components; ++i)
2560 boundary_topology.dof_indices[physical_cell][i] = dof_indices[i];
2561
2562 const auto interior_face = GeometryInfo<dim>::opposite_face[boundary_face_no];
2563 AssertThrow(!is_physical_boundary_face(cell, interior_face),
2564 ExcMessage("KT boundary stencil requires at least two interior cells behind the boundary face."));
2565
2566 auto neighbor = face_neighbor(cell, interior_face);
2567 std::array<types::global_dof_index, n_components> first_interior_dofs{};
2568 neighbor->get_dof_indices(dof_indices);
2569 for (uint i = 0; i < n_components; ++i)
2570 first_interior_dofs[i] = dof_indices[i];
2571
2572 AssertThrow(!is_physical_boundary_face(neighbor, interior_face),
2573 ExcMessage("KT boundary stencil requires a second interior cell behind the boundary face."));
2574 auto next_neighbor = face_neighbor(neighbor, interior_face);
2575 std::array<types::global_dof_index, n_components> second_interior_dofs{};
2576 next_neighbor->get_dof_indices(dof_indices);
2577 for (uint i = 0; i < n_components; ++i)
2578 second_interior_dofs[i] = dof_indices[i];
2579
2580 if (boundary_topology.lower_boundary) {
2581 boundary_topology.x[upper_inner] = neighbor->center();
2582 boundary_topology.x[upper_outer] = next_neighbor->center();
2583 boundary_topology.dof_indices[upper_inner] = first_interior_dofs;
2584 boundary_topology.dof_indices[upper_outer] = second_interior_dofs;
2585 } else {
2586 boundary_topology.x[lower_inner] = neighbor->center();
2587 boundary_topology.x[lower_outer] = next_neighbor->center();
2588 boundary_topology.dof_indices[lower_inner] = first_interior_dofs;
2589 boundary_topology.dof_indices[lower_outer] = second_interior_dofs;
2590 }
2591 }
2592
2594 {
2595 FEValues<dim> fe_values(mapping, fe, quadrature, update_quadrature_points | update_JxW_values);
2596 cell_topology_cache.clear();
2597 cell_topology_cache.resize(triangulation.n_active_cells());
2598
2599 std::vector<types::global_dof_index> dof_indices(fe.dofs_per_cell);
2600 // Every cell, not just the owned ones: the stencil reaches two cells out, so an owned
2601 // cell at a partition boundary needs entries for cells it does not own.
2602 for (const auto &cell : dof_handler.active_cell_iterators()) {
2603 auto &cache_entry = cell_topology_cache[cell->active_cell_index()];
2604 auto &stencil_topology = cache_entry.stencil;
2605 stencil_topology.neighbors = {};
2606 stencil_topology.boundary_ids.fill(numbers::invalid_boundary_id);
2607 stencil_topology.face_centers = {};
2608 for (auto &neighbor_dofs : stencil_topology.neighbors.dof_indices)
2609 neighbor_dofs.fill(numbers::invalid_dof_index);
2610 for (auto &boundary_reconstruction_topology : cache_entry.boundary_stencils) {
2611 boundary_reconstruction_topology.tangential_ghost_neighbor_valid.fill(false);
2612 boundary_reconstruction_topology.corner_tangential_stencil_valid.fill(false);
2613 for (auto &dofs : boundary_reconstruction_topology.primary.dof_indices)
2614 dofs.fill(numbers::invalid_dof_index);
2615 for (auto &topology : boundary_reconstruction_topology.tangential_ghost_neighbors)
2616 for (auto &dofs : topology.dof_indices)
2617 dofs.fill(numbers::invalid_dof_index);
2618 for (auto &corner_stencils : boundary_reconstruction_topology.corner_tangential_stencils)
2619 for (auto &topology : corner_stencils)
2620 for (auto &dofs : topology.dof_indices)
2621 dofs.fill(numbers::invalid_dof_index);
2622 }
2623
2624 fe_values.reinit(cell);
2625 cell->get_dof_indices(dof_indices);
2626 stencil_topology.cell.x = cell->center();
2627 for (uint i = 0; i < n_components; ++i)
2628 stencil_topology.cell.dof_indices[i] = dof_indices[i];
2629 cache_entry.quadrature_points = fe_values.get_quadrature_points();
2630 cache_entry.jxw.assign(fe_values.get_JxW_values().begin(), fe_values.get_JxW_values().end());
2631 cache_entry.cell_width = DiFfRG::internal::cell_width(cell);
2632
2633 for (const auto face_index : cell->face_indices()) {
2634 const auto face = cell->face(face_index);
2635 stencil_topology.face_centers[face_index] = face->center();
2636 if (is_physical_boundary_face(cell, face_index)) {
2637 stencil_topology.boundary_ids[face_index] = face->boundary_id();
2638 stencil_topology.neighbors.x[face_index] = face->center();
2639 auto &boundary_reconstruction_topology = cache_entry.boundary_stencils[face_index];
2640 fill_boundary_topology(boundary_reconstruction_topology.primary, cell, face_index, dof_indices);
2641
2642 if constexpr (dim == 2) {
2643 const unsigned int normal_axis = face_index / 2;
2644 const unsigned int tangential_axis = 1U - normal_axis;
2645 const unsigned int tangential_minus = 2 * tangential_axis;
2646 const unsigned int tangential_plus = tangential_minus + 1;
2647 const auto interior_face = GeometryInfo<dim>::opposite_face[face_index];
2648 auto normal_neighbor = face_neighbor(cell, interior_face);
2649 auto second_normal_neighbor = face_neighbor(normal_neighbor, interior_face);
2650
2651 for (const auto tangential_face : {tangential_minus, tangential_plus}) {
2652 if (!is_physical_boundary_face(cell, tangential_face)) {
2653 const auto tangential_neighbor = face_neighbor(cell, tangential_face);
2654 if (is_physical_boundary_face(tangential_neighbor, face_index)) {
2656 boundary_reconstruction_topology.tangential_ghost_neighbors[tangential_face],
2657 tangential_neighbor, face_index, dof_indices);
2658 boundary_reconstruction_topology.tangential_ghost_neighbor_valid[tangential_face] = true;
2659 }
2660 continue;
2661 }
2662
2664 boundary_reconstruction_topology.corner_tangential_stencils[tangential_face][0], cell,
2665 tangential_face, dof_indices);
2667 boundary_reconstruction_topology.corner_tangential_stencils[tangential_face][1],
2668 normal_neighbor, tangential_face, dof_indices);
2670 boundary_reconstruction_topology.corner_tangential_stencils[tangential_face][2],
2671 second_normal_neighbor, tangential_face, dof_indices);
2672 boundary_reconstruction_topology.corner_tangential_stencil_valid[tangential_face] = true;
2673 }
2674 }
2675 continue;
2676 }
2677
2678 const auto neighbor = face_neighbor(cell, face_index);
2679 neighbor->get_dof_indices(dof_indices);
2680 stencil_topology.neighbors.x[face_index] = neighbor->center();
2681 for (uint i = 0; i < n_components; ++i)
2682 stencil_topology.neighbors.dof_indices[face_index][i] = dof_indices[i];
2683 }
2684
2685 for (const auto face_index : cell->face_indices())
2686 build_face_jacobian_dependency_cache(cell, face_index,
2687 cache_entry.face_jacobian_dependencies[face_index]);
2688 build_source_jacobian_dependency_cache(cell, cache_entry.source_jacobian_dependencies);
2689 }
2690 }
2691
2693 {
2694 const auto cell_index = cell->active_cell_index();
2695 AssertIndexRange(cell_index, cell_topology_cache.size());
2696 return cell_topology_cache[cell_index];
2697 }
2698 SummaryEvent summary() const override
2699 {
2700 SummaryEvent result{.component = "FV"};
2701 result.timing("reinit", average_time_reinit() * 1000, num_reinits())
2702 .timing("residual", average_time_residual_assembly() * 1000, num_residuals())
2704 return result;
2705 }
2706
2707 double average_time_reinit() const
2708 {
2709 double t = 0.;
2710 double n = timings_reinit.size();
2711 for (const auto &t_ : timings_reinit)
2712 t += t_ / n;
2713 return t;
2714 }
2715 uint num_reinits() const { return timings_reinit.size(); }
2716
2718 {
2719 double t = 0.;
2720 double n = timings_residual.size();
2721 for (const auto &t_ : timings_residual)
2722 t += t_ / n;
2723 return t;
2724 }
2725 uint num_residuals() const { return timings_residual.size(); }
2726
2728 {
2729 double t = 0.;
2730 double n = timings_jacobian.size();
2731 for (const auto &t_ : timings_jacobian)
2732 t += t_ / n;
2733 return t;
2734 }
2735 uint num_jacobians() const { return timings_jacobian.size(); }
2736
2737 protected:
2741 const DoFHandler<dim> &dof_handler;
2742 const Mapping<dim> &mapping;
2743 const Triangulation<dim> &triangulation;
2744 const FiniteElement<dim> &fe;
2745
2747 AssemblySchedule schedule_for(const double cost_ns) const
2748 {
2750 }
2751
2754 {
2755 const uint n_owned = n_locally_owned_cells(discretization);
2756 const bool unchanged = n_owned == n_owned_cells;
2757 n_owned_cells = n_owned;
2758 if (unchanged) return;
2759
2760 const uint threads = DiFfRG::n_threads();
2761 const auto cheap = schedule_for(assembly_cost::local_fe);
2762 const auto integral = schedule_for(assembly_cost::momentum_integral);
2763 report_port.info("FV: Assembling {} cells on {} threads -- {}x{} workers/cells for a cheap cell loop, "
2764 "{}x{} for an integral one.",
2765 n_owned_cells, threads, cheap.queue_length, cheap.chunk_size, integral.queue_length,
2766 integral.chunk_size);
2767 }
2768
2772
2773 mutable Point EoM;
2777 mutable std::optional<Point> EoM_minimum_guess;
2780
2781 const QGauss<dim> quadrature;
2782 const QGauss<dim - 1> quadrature_face;
2783
2787
2788 std::vector<double> timings_reinit;
2789 std::vector<double> timings_residual;
2790 std::vector<double> timings_jacobian;
2791 std::array<unsigned int, n_components> local_component_of_dof{};
2792 std::vector<CellTopologyCacheEntry> cell_topology_cache;
2793 std::vector<FaceReconstructionDescriptor> face_reconstruction_descriptors;
2796
2797 // Warn once per assembler when round-off from a gradient-independent flux baseline
2798 // exceeds this fraction of the physical flux variation; see
2799 // probe_diffusion_flux_conditioning(). 1e-9 sits comfortably below the tolerances any
2800 // fRG flow is run at, so a warning always means real digits are being lost.
2801 static constexpr double flux_conditioning_warn_threshold = 1e-9;
2802 static constexpr unsigned int flux_conditioning_max_samples = 512;
2803 // Set in reinit() so the collective that produces it is never behind a condition.
2804 double domain_diameter = 0.0;
2805 // Opt-in ("/discretization/diagnose_flux_conditioning"). The baseline/variation ratio
2806 // is a sound measure of digits lost in the flux difference, but on its own it cannot
2807 // tell a harmful case from a harmless one: a model may be baseline-dominated in the
2808 // deep UV while its diffusion term is still irrelevant to the flow, and pay nothing
2809 // for it. Enable this when a KT flow is inexplicably slow or stalls at tight
2810 // tolerances, and read the ratio as "digits available in the diffusion residual".
2812 mutable bool flux_conditioning_probed = false;
2813 };
2814 } // namespace KurganovTadmor
2815 } // namespace FV
2816} // namespace DiFfRG
This is the general assembler interface for any kind of discretization. An assembler is responsible f...
Definition abstract_assembler.hh:54
Definition affine_constraint_metadata.hh:24
A hierarchical configuration tree, readable from JSON and TOML files.
Definition config_tree.hh:32
typename DiFfRG::internal::components_of< ModelOrComponents_ >::type Components
Definition discretization.hh:50
static constexpr uint dim
Definition discretization.hh:55
SparseMatrixType_ SparseMatrixType
Definition discretization.hh:53
NumberType_ NumberType
Definition discretization.hh:51
VectorType_ VectorType
Definition discretization.hh:52
Definition KurganovTadmor.hh:645
static bool is_physical_boundary_face(const Iterator &cell, const unsigned int face_index)
Definition KurganovTadmor.hh:1176
unsigned int find_neighbor_face(const Iterator &cell, const Iterator &neighbor) const
Definition KurganovTadmor.hh:1350
virtual void attach_data_output(OutputFrame< dim, VectorType > &data_out, const VectorType &solution, const VectorType &variables, const VectorType &dt_solution=VectorType(), const VectorType &residual=VectorType()) override
Definition KurganovTadmor.hh:775
static void fill_cell_data(const Iterator &cell, const VectorType &solution_global, std::vector< types::global_dof_index > &scratch_dof_indices, CellData &data)
Definition KurganovTadmor.hh:1165
void build_face_jacobian_dependency_cache(const Iterator &cell, const unsigned int face_index, FaceJacobianDependencyCacheEntry &dependencies) const
Definition KurganovTadmor.hh:2414
virtual void reinit() override
Reinitialize the assembler. This is necessary if the mesh has changed, e.g. after a mesh refinement.
Definition KurganovTadmor.hh:795
virtual void reinit_matrix(SparseMatrixType &matrix) const override
Definition KurganovTadmor.hh:753
Discretization & discretization
Definition KurganovTadmor.hh:2738
void build_cached_jacobian_sparsity(get_type::SparsityPattern< SparseMatrixType > &sparsity_pattern) const
Definition KurganovTadmor.hh:2518
std::optional< Point > EoM_minimum_guess
Definition KurganovTadmor.hh:2777
std::vector< CellTopologyCacheEntry > cell_topology_cache
Definition KurganovTadmor.hh:2792
auto compute_interior_face_reconstruction_from_cache(const Iterator &cell, const Iterator &ncell, const VectorType &solution_global, const Point &x_q, Scratch &scratch_data) const
Definition KurganovTadmor.hh:1508
uint num_reinits() const
Definition KurganovTadmor.hh:2715
const QGauss< dim - 1 > quadrature_face
Definition KurganovTadmor.hh:2782
static constexpr int nothing
Definition KurganovTadmor.hh:649
Iterator EoM_cell
Definition KurganovTadmor.hh:2774
WaveSpeedStrategy_ WaveSpeedStrategy
Definition KurganovTadmor.hh:685
virtual void refinement_indicator(Vector< double > &indicator, const VectorType &solution_global)
Definition KurganovTadmor.hh:2349
auto fv_tie(T &&...t)
The named tuple handed to model.source(), the FV counterpart of CG's fe_tie().
Definition KurganovTadmor.hh:662
JacobianReconstructor_ JacobianReconstructor
Definition KurganovTadmor.hh:686
void fill_cell_stencil(const Iterator &cell, const VectorType &solution_global, CellStencilData &stencil) const
Definition KurganovTadmor.hh:1324
auto compute_boundary_jacobian_face_reconstruction_from_cache(const Iterator &cell, const unsigned int face_no, const VectorType &solution_global, const Point &x_q) const
Definition KurganovTadmor.hh:1539
virtual void jacobian(SparseMatrixType &jacobian, const VectorType &solution_global, NumberType weight, const VectorType &solution_global_dot, NumberType alpha, NumberType beta, const VectorType &variables=VectorType()) override
Definition KurganovTadmor.hh:1954
get_type::SparsityPattern< SparseMatrixType > sparsity_pattern_mass
Definition KurganovTadmor.hh:2784
static constexpr unsigned int flux_conditioning_max_samples
Definition KurganovTadmor.hh:2802
std::vector< double > timings_jacobian
Definition KurganovTadmor.hh:2790
virtual const SparseMatrixType & get_mass_matrix() const override
Obtain the mass matrix.
Definition KurganovTadmor.hh:853
get_type::SparsityPattern< SparseMatrixType > sparsity_pattern_jacobian
Definition KurganovTadmor.hh:2785
const AssemblyScheduleOverrides schedule_overrides
Definition KurganovTadmor.hh:2771
SolutionReconstructionCache residual_reconstruction_cache
Definition KurganovTadmor.hh:2794
ReportPort report_port
Definition KurganovTadmor.hh:2740
double average_time_reinit() const
Definition KurganovTadmor.hh:2707
void fill_boundary_topology(internal::BoundaryStencilTopologyData< dim, n_components > &boundary_topology, const Iterator &cell, const unsigned int boundary_face_no, std::vector< types::global_dof_index > &dof_indices) const
Definition KurganovTadmor.hh:2543
typename Discretization::Components Components
Definition KurganovTadmor.hh:691
void initialize_solution_reconstruction_cache_topology(SolutionReconstructionCache &cache) const
Definition KurganovTadmor.hh:1417
void refresh_solution_reconstruction_cache_values(const VectorType &solution_global, SolutionReconstructionCache &cache) const
Definition KurganovTadmor.hh:1435
double average_time_residual_assembly() const
Definition KurganovTadmor.hh:2717
static void append_dofs(std::vector< types::global_dof_index > &target, const DoFContainer &source)
Definition KurganovTadmor.hh:2355
static void sort_unique_dofs(std::vector< types::global_dof_index > &dofs)
Definition KurganovTadmor.hh:2367
void build_sparsity(get_type::SparsityPattern< SparseMatrixType > &sparsity_pattern, const DoFHandler< dim > &to_dofh, const DoFHandler< dim > &from_dofh, const int stencil=2, bool add_extractor_dofs=false) const
Definition KurganovTadmor.hh:2460
SummaryEvent summary() const override
Definition KurganovTadmor.hh:2698
virtual IndexSet get_differential_indices() const override
Obtain the dofs which contain time derivatives.
Definition KurganovTadmor.hh:767
Reconstructor_ Reconstructor
Definition KurganovTadmor.hh:684
auto compute_interior_jacobian_face_reconstruction_from_cache(const Iterator &cell, const Iterator &ncell, const VectorType &solution_global, const Point &x_q, Scratch &scratch_data) const
Definition KurganovTadmor.hh:1529
std::vector< double > timings_reinit
Definition KurganovTadmor.hh:2788
static constexpr double flux_conditioning_warn_threshold
Definition KurganovTadmor.hh:2801
static Tensor< 1, dim > face_normal_from_cell(const Iterator &cell, const unsigned int face_no)
Definition KurganovTadmor.hh:1705
void run_fv_kt_pre_assembly_hook(const AssemblyStage stage, const Context &context)
Definition KurganovTadmor.hh:1160
void append_reconstruction_neighbor_dofs(std::vector< types::global_dof_index > &from_dofs, const Iterator &root_cell) const
Definition KurganovTadmor.hh:2373
void fill_constant_quadrature_values(const Iterator &cell, const VectorType &solution_global, const VectorType &solution_global_dot, Scratch &scratch_data) const
Definition KurganovTadmor.hh:1653
typename DoFHandler< Discretization::dim >::active_cell_iterator Iterator
Definition KurganovTadmor.hh:701
static constexpr uint dim
Definition KurganovTadmor.hh:692
const FaceReconstructionState & get_cached_face_reconstruction(const SolutionReconstructionCache &cache, const Iterator &cell, const unsigned int face_index) const
Definition KurganovTadmor.hh:1496
static constexpr auto v_tie(T &&...t)
Definition KurganovTadmor.hh:669
internal::BoundaryReconstructionStencilData< dim, BoundaryNumberType, n_components > build_boundary_reconstruction_stencil_from_cache(const Iterator &cell, const unsigned int boundary_face_no, const VectorType &solution_global) const
Definition KurganovTadmor.hh:1316
void rebuild_face_reconstruction_descriptors()
Definition KurganovTadmor.hh:1465
virtual void jacobian_variables(FullMatrix< NumberType > &jacobian, const VectorType &variables, const VectorType &) override
Definition KurganovTadmor.hh:868
bool flux_conditioning_probed
Definition KurganovTadmor.hh:2812
std::vector< FaceReconstructionDescriptor > face_reconstruction_descriptors
Definition KurganovTadmor.hh:2793
internal::BoundaryStencilData< boundary_dim, BoundaryNumberType, n_components > build_boundary_stencil_from_cache_impl(const Iterator &cell, const unsigned int boundary_face_no, const VectorType &solution_global) const
Definition KurganovTadmor.hh:1297
void update_assembly_schedules()
Definition KurganovTadmor.hh:2753
virtual void residual_variables(VectorType &residual, const VectorType &variables, const VectorType &spatial_solution) override
Definition KurganovTadmor.hh:855
typename Discretization::SparseMatrixType SparseMatrixType
Definition KurganovTadmor.hh:689
virtual void reinit_vector(VectorType &vec) const override
Definition KurganovTadmor.hh:748
CellStencilDataT< autodiff::Real< 1, NumberType > > tag_cell_stencil_dofs_from_cache(const Iterator &cell, const CellStencilData &cell_stencil, const VectorType &solution_global, const types::global_dof_index dof_j) const
Definition KurganovTadmor.hh:1331
uint num_jacobians() const
Definition KurganovTadmor.hh:2735
virtual void set_time(double t) override
Set the current time. The assembler should usually just forward this to the numerical model.
Definition KurganovTadmor.hh:847
virtual MPI_Comm get_communicator() const override
The communicator this assembler's linear algebra lives on.
Definition KurganovTadmor.hh:759
virtual void mass(VectorType &mass, const VectorType &solution_global, const VectorType &solution_global_dot, NumberType weight) override
Definition KurganovTadmor.hh:1077
void rebuild_solution_reconstruction_cache(const VectorType &solution_global, SolutionReconstructionCache &cache) const
Definition KurganovTadmor.hh:1361
static constexpr uint n_faces
Definition KurganovTadmor.hh:697
void fill_cell_data_from_topology(const CellGeometryDofs &topology, const VectorType &solution_global, CellData &data) const
Definition KurganovTadmor.hh:1289
const DoFHandler< dim > & dof_handler
Definition KurganovTadmor.hh:2741
const QGauss< dim > quadrature
Definition KurganovTadmor.hh:2781
internal::ThirdDerivativeType< dim, NumberType, n_components > ThirdDerivativeType
Definition KurganovTadmor.hh:700
static constexpr uint n_components
Definition KurganovTadmor.hh:696
auto make_assembly_context_view(const SolutionReconstructionCache &cache) const
Definition KurganovTadmor.hh:1147
uint n_owned_cells
Definition KurganovTadmor.hh:2770
static GradientType source_gradient(const CellStencilData &stencil, const Point &x_q)
The "fe_derivatives" slot of fv_tie(), i.e. the gradient model.source() sees at x_q.
Definition KurganovTadmor.hh:1678
const FiniteElement< dim > & fe
Definition KurganovTadmor.hh:2744
SparseMatrixType mass_matrix
Definition KurganovTadmor.hh:2786
void readouts(OutputFrame< dim, VectorType > &data_out, const VectorType &solution_global, const VectorType &variables) const
Definition KurganovTadmor.hh:881
virtual void residual(VectorType &residual, const VectorType &solution_global, NumberType weight, const VectorType &solution_global_dot, NumberType weight_mass, const VectorType &variables=VectorType()) override
Definition KurganovTadmor.hh:1721
auto extractor_raw_potential(const VectorType &solution_global) const
Evaluate the model's extractors at the EoM point.
Definition KurganovTadmor.hh:1038
void probe_diffusion_flux_conditioning(const SolutionReconstructionCache &reconstruction_cache, const ExtractorArray &extractors, const VectorType &variables) const
One-shot conditioning check on the model's diffusion flux.
Definition KurganovTadmor.hh:1571
const Mapping< dim > & mapping
Definition KurganovTadmor.hh:2742
Point EoM
Definition KurganovTadmor.hh:2773
virtual const get_type::SparsityPattern< SparseMatrixType > & get_sparsity_pattern_jacobian() const override
Obtain the sparsity pattern of the jacobian matrix.
Definition KurganovTadmor.hh:849
void initialize_cell_stencil_topology(const unsigned int cell_index, CellStencilData &stencil) const
Definition KurganovTadmor.hh:1424
std::pair< Point, Iterator > resolve_extractor_point(const Point &EoM_point, const Iterator &EoM_cell_, const VectorType &solution_global) const
Where the model wants its extractors evaluated, and the cell holding that point.
Definition KurganovTadmor.hh:949
static void append_valid_dofs(std::vector< types::global_dof_index > &target, const DoFContainer &source)
Definition KurganovTadmor.hh:2361
const Triangulation< dim > & triangulation
Definition KurganovTadmor.hh:2743
virtual void reinit_solution_view(SolutionView< VectorType > &view) const override
Definition KurganovTadmor.hh:761
void rebuild_cell_topology_cache()
Definition KurganovTadmor.hh:2593
std::vector< double > timings_residual
Definition KurganovTadmor.hh:2789
SolutionReconstructionCache jacobian_reconstruction_cache
Definition KurganovTadmor.hh:2795
dealii::Point< dim > Point
Definition KurganovTadmor.hh:702
Model & model
Definition KurganovTadmor.hh:2739
double domain_diameter
Definition KurganovTadmor.hh:2804
internal::BoundaryStencilData< dim, BoundaryNumberType, n_components > build_boundary_stencil_from_cache(const Iterator &cell, const unsigned int boundary_face_no, const VectorType &solution_global) const
Definition KurganovTadmor.hh:1307
typename Discretization::NumberType NumberType
Definition KurganovTadmor.hh:687
void build_source_jacobian_dependency_cache(const Iterator &cell, FaceJacobianDependencyCacheEntry &dependencies) const
The dofs the nonlocal part of the source jacobian writes to and reads from.
Definition KurganovTadmor.hh:2442
void ensure_solution_reconstruction_cache_shape(SolutionReconstructionCache &cache) const
Definition KurganovTadmor.hh:1404
void append_boundary_reconstruction_dofs(std::vector< types::global_dof_index > &from_dofs, const unsigned int cell_index, const unsigned int face_index) const
Definition KurganovTadmor.hh:2393
void refresh_cell_stencil_values(const unsigned int cell_index, const VectorType &solution_global, CellStencilData &stencil) const
Definition KurganovTadmor.hh:1442
typename Discretization::VectorType VectorType
Definition KurganovTadmor.hh:688
virtual void jacobian_mass(SparseMatrixType &jacobian, const VectorType &solution_global, const VectorType &solution_global_dot, NumberType alpha=1., NumberType beta=1.) override
Definition KurganovTadmor.hh:1896
internal::GradientType< dim, NumberType, n_components > GradientType
Definition KurganovTadmor.hh:699
DiFfRG::internal::PotentialSystemCache< dim, NumberType > potential_cache
Mesh-dependent half of the potential reconstructions, built once and reused; see PotentialSystemCache...
Definition KurganovTadmor.hh:2779
auto make_assembly_context_view(SolutionReconstructionCache &&cache) const =delete
static double face_jxw(const Iterator &cell, const unsigned int face_no)
Definition KurganovTadmor.hh:1713
AssemblySchedule schedule_for(const double cost_ns) const
Definition KurganovTadmor.hh:2747
const bool diagnose_flux_conditioning
Definition KurganovTadmor.hh:2811
Discretization_ Discretization
Definition KurganovTadmor.hh:682
static constexpr auto e_tie(T &&...t)
Definition KurganovTadmor.hh:674
void extract(std::array< NumberType, Components::count_extractors()> &data, const VectorType &solution_global, const VectorType &variables, bool search_EoM, bool set_EoM, bool postprocess) const
Definition KurganovTadmor.hh:1049
double average_time_jacobian_assembly() const
Definition KurganovTadmor.hh:2727
ReadoutSolution reconstruct_readout_solution(const Iterator &cell, const VectorType &solution_global, const Point &x, bool with_hessians=false) const
Definition KurganovTadmor.hh:973
auto compute_boundary_face_reconstruction_from_cache(const Iterator &cell, const unsigned int face_no, const VectorType &solution_global, const Point &x_q) const
Definition KurganovTadmor.hh:1518
static void fill_cell_stencil(const Iterator &cell, const VectorType &solution_global, const Model &model, std::vector< types::global_dof_index > &scratch_dof_indices, CellStencilData &stencil)
Definition KurganovTadmor.hh:1186
static Iterator face_neighbor(const Iterator &cell, const unsigned int face_index)
Definition KurganovTadmor.hh:1181
static internal::BoundaryStencilData< dim, BoundaryNumberType, n_components > build_boundary_stencil(const Iterator &cell, const unsigned int boundary_face_no, const VectorType &solution_global, std::vector< types::global_dof_index > &scratch_dof_indices)
Definition KurganovTadmor.hh:1231
Model_ Model
Definition KurganovTadmor.hh:683
std::array< unsigned int, n_components > local_component_of_dof
Definition KurganovTadmor.hh:2791
static std::array< internal::GradientType< dim, NumberType, n_components >, n_faces > compute_neighbor_gradients(const Iterator &cell, const VectorType &solution_global, const Model &model, std::vector< types::global_dof_index > &scratch_dof_indices, CellStencilData &temporary_stencil)
Definition KurganovTadmor.hh:1685
Assembler(Discretization &discretization, Model &model, const ConfigTree &config)
Definition KurganovTadmor.hh:728
const Config::EoMConfig EoM_config
Definition KurganovTadmor.hh:2776
const CellTopologyCacheEntry & get_cell_topology(const Iterator &cell) const
Definition KurganovTadmor.hh:2692
internal::CellStencilData< dim, NT, n_components > CellStencilDataT
Definition KurganovTadmor.hh:1133
Iterator old_EoM_cell
Definition KurganovTadmor.hh:2775
uint num_residuals() const
Definition KurganovTadmor.hh:2725
Definition assembly_context.hh:395
Definition assembly_context.hh:269
Definition assembly_context.hh:123
Decides, without any user input, which rank computes which part of each map().
Definition map_scheduler.hh:195
void attach(const DoFHandler< dim > &dof_handler, const VectorType &solution, const std::string &name)
Definition output_session.hh:47
Definition output_session.hh:42
void attach_raw_potential(ReconstructedRawPotential< dim, typename VectorType::value_type > potential)
Definition output_session.hh:141
FieldCollector fields()
Definition output_session.hh:92
void register_readout(const std::string &id)
Definition output_session.hh:154
void attach_eom_potential(EoMResult< dim, typename VectorType::value_type > result)
Definition output_session.hh:136
Definition run_reporter.hh:96
void warn(spdlog::format_string_t< Args... > format, Args &&...args) const
Definition run_reporter.hh:107
void info(spdlog::format_string_t< Args... > format, Args &&...args) const
Definition run_reporter.hh:103
A simple NxM-matrix class, which is used for cell-wise Jacobians.
Definition tuples.hh:170
A read-only, fully-replicated view of the solution.
Definition solution_view.hh:48
void reinit(const dealii::IndexSet &, const dealii::IndexSet &, MPI_Comm)
Establish the layout. No-op for the serial policy.
Definition solution_view.hh:58
TVD gradient reconstructor parameterised by a slope limiter.
Definition tvd_reconstructor.hh:46
The mesh-dependent half of solve_potential, retained across calls.
Definition eom.hh:315
Whether Model chooses its own point at which the extractors are evaluated.
Definition solution_sample.hh:137
Definition types.hh:9
Concept that any gradient-reconstruction strategy must satisfy.
Definition abstract_reconstructor.hh:22
Concept that any wave-speed strategy must satisfy.
Definition abstract_wave_speed.hh:45
All compile-time knowledge about which linear algebra types DiFfRG uses.
FaceReconstructionState< 1, NumberType, n_components > compute_boundary_face_reconstruction_state(BoundaryStencilData< 1, NumberType, n_components > boundary_stencil, const CellStencilData< 1, NumberType, n_components > &physical_cell_stencil, const dealii::Point< 1 > &x_q, const Model &model)
Definition reconstruction_cache.hh:768
def::GradientType< dim, NumberType, n_components > GradientType
Definition reconstruction_cache.hh:31
std::array< JacobianMatrix< NumberType, n_components >, dim > restrict_jacobian_to_block(const std::array< JacobianMatrix< NumberType, n_components >, dim > &J, const std::array< int, n_components > &blocks, const int block)
Copy of J with every row and column outside block zeroed.
Definition KurganovTadmor.hh:207
BoundaryStencilData< dim, autodiff::Real< 1, NumberType >, n_components > tag_boundary_stencil_dofs(const BoundaryStencilData< dim, NumberType, n_components > &boundary_stencil, dealii::types::global_dof_index dof_j)
Definition reconstruction_cache.hh:859
std::pair< CellStencilData< 1, NumberType, n_components >, CellStencilData< 1, NumberType, n_components > > make_model_boundary_reconstruction_side_stencils(BoundaryStencilData< 1, NumberType, n_components > boundary_stencil, const CellStencilData< 1, NumberType, n_components > &physical_cell_stencil, const dealii::Point< 1 > &x_q, const Model &model)
Definition reconstruction_cache.hh:710
std::array< dealii::Tensor< 1, dim, NumberType >, n_components > compute_diffusion_flux(const std::array< NumberType, n_components > &u_minus, const std::array< NumberType, n_components > &u_plus, const GradientType< dim, NumberType, n_components > &grad_u_minus, const GradientType< dim, NumberType, n_components > &grad_u_plus, const ThirdDerivativeType< dim, NumberType, n_components > &third_derivatives_minus, const ThirdDerivativeType< dim, NumberType, n_components > &third_derivatives_plus, const dealii::Point< dim > &x_q, const double cell_width_minus, const double cell_width_plus, const ExtractorArray &extractors, const VariableVector &variables, const Model &model)
Definition KurganovTadmor.hh:488
std::array< dealii::Tensor< 1, dim, NumberType >, n_components > compute_numerical_flux(const std::array< dealii::Tensor< 1, dim, NumberType >, n_components > &F_plus, const std::array< dealii::Tensor< 1, dim, NumberType >, n_components > &F_minus, const std::array< dealii::Tensor< 1, dim, NumberType >, n_components > &a_half, const std::array< NumberType, n_components > &u_plus, const std::array< NumberType, n_components > &u_minus)
Definition KurganovTadmor.hh:376
auto flux_tie(T &&...t)
The named tuple handed to model.flux().
Definition flux_ties.hh:36
auto diffusion_flux_tie(T &&...t)
The named tuple handed to model.diffusion_flux().
Definition flux_ties.hh:44
std::array< ReconstructionDerivativeData< dim, NumberType, n_components >, 2 > extract_diffusion_face_derivatives(const DiffusionFaceState< dim, autodiff::Real< 1, NumberType >, n_components > &state)
Definition reconstruction_cache.hh:243
FaceReconstructionState< dim, NumberType, n_components > compute_interior_face_reconstruction_state(const CellStencilData< dim, NumberType, n_components > &minus_stencil, const CellStencilData< dim, NumberType, n_components > &plus_stencil, const dealii::Point< dim > &x_q)
Definition reconstruction_cache.hh:265
BoundaryReconstructionStencilData< dim, autodiff::Real< 1, NumberType >, n_components > tag_boundary_reconstruction_stencil_dofs(const BoundaryReconstructionStencilData< dim, NumberType, n_components > &boundary_reconstruction_stencil, dealii::types::global_dof_index dof_j)
Definition reconstruction_cache.hh:883
void apply_boundary_reconstruction_stencil(BoundaryStencilData< dim, NumberType, n_components > &boundary_stencil, const CellStencilData< dim, NumberType, n_components > &cell_stencil, const Model &model)
Definition reconstruction_cache.hh:504
FluxDerivativeData< NumberType, dim, n_components > compute_flux_derivatives_ad(const std::array< NumberType, n_components > &u, const std::array< dealii::Tensor< 1, dim, NumberType >, n_components > &grad_u, const dealii::Point< dim > &x_q, const double cell_width, const ExtractorArray &extractors, const VariableVector &variables, const Model &model)
Compute F, dF/du, d2F/du2, dF/dgrad(u), and d2F/(du dgrad(u)) with second-order forward AD.
Definition flux_jacobian_hessian.hh:56
BoundaryStencilData< dim, BoundaryNumberType, n_components > fill_boundary_stencil_from_topology(const BoundaryStencilTopologyData< dim, n_components > &topology, const VectorType &solution_global)
Definition reconstruction_cache.hh:371
KTFluxData< dim, NumberType, n_components > compute_kt_flux_and_speeds(const std::array< NumberType, n_components > &u_plus, const std::array< NumberType, n_components > &u_minus, const GradientType< dim, NumberType, n_components > &grad_u_plus, const GradientType< dim, NumberType, n_components > &grad_u_minus, const dealii::Point< dim > &x_q, const double cell_width_plus, const double cell_width_minus, const ExtractorArray &extractors, const VariableVector &variables, const Model &model)
Definition KurganovTadmor.hh:280
CellStencilData< dim, autodiff::Real< 1, NumberType >, n_components > tag_cell_stencil_dofs(const CellStencilData< dim, NumberType, n_components > &cell_stencil, dealii::types::global_dof_index dof_j)
Definition reconstruction_cache.hh:846
FourPointStencil< dim, NumberType, n_components > make_boundary_third_derivative_stencil(const BoundaryStencilData< dim, NumberType, n_components > &boundary_stencil)
Definition reconstruction_cache.hh:205
KTNumFluxJacobianData< dim, NumberType, n_components > compute_kt_numflux_jacobian(const std::array< NumberType, n_components > &u_plus, const std::array< NumberType, n_components > &u_minus, const GradientType< dim, NumberType, n_components > &grad_u_plus, const GradientType< dim, NumberType, n_components > &grad_u_minus, const dealii::Point< dim > &x_q, const double cell_width_plus, const double cell_width_minus, const ExtractorArray &extractors, const VariableVector &variables, const Model &model)
Definition KurganovTadmor.hh:400
void populate_boundary_neighbor_from_model_stencil(BoundaryStencilData< 1, NumberType, n_components > &boundary_stencil, CellStencilData< 1, NumberType, n_components > &cell_stencil, const unsigned int face_index, const dealii::Point< 1 > &x_q, const Model &model)
Definition reconstruction_cache.hh:672
def::ThirdDerivativeType< dim, NumberType, n_components > ThirdDerivativeType
Definition reconstruction_cache.hh:34
std::array< std::array< std::array< std::array< NumberType, n_components >, n_components >, n_components >, dim > HessianTensor
Definition flux_jacobian_hessian.hh:24
FaceReconstructionState< dim, NumberType, n_components > reverse_face_reconstruction(const FaceReconstructionState< dim, NumberType, n_components > &state)
Definition reconstruction_cache.hh:74
std::array< std::array< NumberType, n_components >, n_components > JacobianMatrix
Definition flux_jacobian_hessian.hh:21
FourPointStencil< dim, NumberType, n_components > make_interior_third_derivative_stencil(const CellStencilData< dim, NumberType, n_components > &minus_stencil, const CellStencilData< dim, NumberType, n_components > &plus_stencil, const dealii::Point< dim > &x_q)
Definition reconstruction_cache.hh:183
DiffusionFaceState< dim, NumberType, n_components > compute_diffusion_face_state(const CellStencilData< dim, NumberType, n_components > &minus_stencil, const CellStencilData< dim, NumberType, n_components > &plus_stencil)
Definition reconstruction_cache.hh:232
DiffusionFluxJacobianData< dim, NumberType, n_components > compute_diffusion_flux_jacobian(const std::array< NumberType, n_components > &u_minus, const std::array< NumberType, n_components > &u_plus, const GradientType< dim, NumberType, n_components > &grad_u_minus, const GradientType< dim, NumberType, n_components > &grad_u_plus, const ThirdDerivativeType< dim, NumberType, n_components > &third_derivatives_minus, const ThirdDerivativeType< dim, NumberType, n_components > &third_derivatives_plus, const dealii::Point< dim > &x_q, const double cell_width_minus, const double cell_width_plus, const ExtractorArray &extractors, const VariableVector &variables, const Model &model)
Definition KurganovTadmor.hh:520
std::array< NumberType, n_components > reconstruct_u(const std::array< NumberType, n_components > &u_center, const dealii::Point< dim > &center, const dealii::Point< dim > &x, const GradientType< dim, NumberType, n_components > &u_grad)
Definition reconstruction_cache.hh:144
void fill_cell_data_from_topology(const CellGeometryDofs< dim, n_components > &topology, const VectorType &solution_global, CellData< dim, NumberType, n_components > &data)
Definition reconstruction_cache.hh:252
int sgn(T val)
Definition KurganovTadmor.hh:196
BoundaryReconstructionStencilData< dim, BoundaryNumberType, n_components > fill_boundary_reconstruction_stencil_from_topology(const BoundaryReconstructionStencilTopologyData< dim, n_components > &topology, const VectorType &solution_global)
Definition reconstruction_cache.hh:396
std::array< NumberType, n_components > reconstruct_u_derivative(const std::array< autodiff::Real< 1, NumberType >, n_components > &u_center, const dealii::Point< dim > &center, const dealii::Point< dim > &x, const std::array< dealii::Point< dim >, 2 *dim > &x_n, const std::array< std::array< autodiff::Real< 1, NumberType >, n_components >, 2 *dim > &u_n)
Definition reconstruction_cache.hh:300
HessianTensor< NumberType, dim, n_components > restrict_hessian_to_block(const HessianTensor< NumberType, dim, n_components > &H, const std::array< int, n_components > &blocks, const int block)
Copy of H with the two jacobian indices restricted to block.
Definition KurganovTadmor.hh:228
std::array< Result, n_components > per_block(const std::array< int, n_components > &blocks, const ComputeFUN &compute)
Run compute once per distinct block and hand each component its block's result.
Definition KurganovTadmor.hh:252
AssemblyStage
Definition assembly_context.hh:20
void dispatch_fv_kt_pre_assembly(Model &model, const AssemblyStage stage, const Context &context)
Definition assembly_context.hh:448
constexpr double local_fe
Local FE work only: mass matrices, refinement indicators. ~0.2-0.3 us/cell for CG p=3.
Definition assembly_schedule.hh:48
constexpr double momentum_integral
Definition assembly_schedule.hh:53
typename internal::_SparsityPattern< SparseMatrixType >::value SparsityPattern
Definition linear_algebra.hh:155
AffineConstraintMetadata< dim > build_affine_constraint_metadata(const Discretization &discretization)
Definition affine_constraint_metadata.hh:99
double cell_width(const CellIterator &cell)
The smallest face-normal width over all faces of cell.
Definition cell_geometry.hh:42
void apply_model_affine_constraints(Model &model, Constraints &constraints, const Context &context)
Definition affine_constraint_metadata.hh:79
constexpr void validate_readout_helper_arity()
Definition abstract_assembler.hh:17
Definition complex_math.hh:10
auto locally_owned_cells(const dealii::DoFHandler< dim > &dof_handler)
The cells this rank assembles.
Definition assembly_schedule.hh:154
ReconstructedRawPotential< dim, typename VectorType::value_type > reconstruct_raw_potential(const VectorType &sol, const dealii::DoFHandler< dim > &dof_handler, const dealii::Mapping< dim > &mapping, const GradientFUN &get_gradient, const Config::EoMConfig &config, internal::PotentialSystemCache< dim, typename VectorType::value_type > *cache=nullptr)
Reconstruct a scalar raw potential without locating its minimum.
Definition eom.hh:1411
AssemblySchedule make_assembly_schedule(const uint n_local_cells, const uint thread_budget, const double cost_ns, const AssemblyScheduleOverrides &overrides={})
Derive one mesh_loop schedule from the cost of a cell.
Definition assembly_schedule.hh:114
uint n_locally_owned_cells(const Discretization &discretization)
How many cells locally_owned_cells() yields.
Definition assembly_schedule.hh:167
dealii::IndexSet restrict_to_owned(const dealii::IndexSet &global_set, const dealii::IndexSet &locally_owned)
Restrict a global index set to what this rank may write.
Definition la_policy.hh:114
@ config
/discretization/threads.
unsigned int n_threads()
The CPU thread budget this process resolved.
UnusedPotentialEvaluation evaluate_raw_potential(const UnusedPotential &, const dealii::Mapping< dim > &, const dealii::Point< dim > &)
Definition eom.hh:149
void reinit_la_matrix(SparseMatrixType &matrix, const get_type::SparsityPattern< SparseMatrixType > &pattern, const dealii::IndexSet &locally_owned, MPI_Comm comm)
Size a matrix from a finalized sparsity pattern.
Definition la_policy.hh:92
void reinit_la_vector(VectorType &vec, const dealii::IndexSet &locally_owned, MPI_Comm comm)
Size a vector to the rank's share of the rows.
Definition la_policy.hh:27
unsigned int uint
Definition utils.hh:24
EoMResult< dim, typename VectorType::value_type > get_EoM_point_with_potential(typename dealii::DoFHandler< dim >::cell_iterator &EoM_cell, const VectorType &sol, const dealii::DoFHandler< dim > &dof_handler, const dealii::Mapping< dim > &mapping, const EoMFUN &get_EoM, const EoMPFUN &EoM_postprocess, const Config::EoMConfig &config, const std::optional< dealii::Point< dim > > &initial_guess=std::nullopt, internal::PotentialSystemCache< dim, typename VectorType::value_type > *cache=nullptr)
Reconstruct a potential whose gradient approximates the model EoM vector field and return a sampled a...
Definition eom.hh:1486
SolutionSample< dim, NumberType > make_solution_sample(const dealii::DoFHandler< dim > &dof_handler, const dealii::Mapping< dim > &mapping, const uint n_components, const FillFUN &fill)
Build a SolutionSample, taking values and gradients from a callback.
Definition solution_sample.hh:154
void finalize_la_sparsity(dealii::DynamicSparsityPattern &dsp, get_type::SparsityPattern< SparseMatrixType > &pattern, const dealii::IndexSet &locally_owned, const dealii::IndexSet &locally_relevant, MPI_Comm comm)
Turn a freshly built DynamicSparsityPattern into the pattern type the matrix wants.
Definition la_policy.hh:50
Overrides, if really wanted by the user: /discretization/{mesh_workers,batch_size}...
Definition assembly_schedule.hh:82
The two trailing arguments of dealii::MeshWorker::mesh_loop.
Definition assembly_schedule.hh:30
Definition eom_config.hh:10
dealii::Tensor< 1, dim > normal(const Iterator &cell, const unsigned int face_index) const
Definition KurganovTadmor.hh:1136
double jxw(const Iterator &cell, const unsigned int face_index) const
Definition KurganovTadmor.hh:1141
FaceJacobianDependencyCacheEntry source_jacobian_dependencies
Definition KurganovTadmor.hh:721
double cell_width
Definition KurganovTadmor.hh:726
std::array< internal::BoundaryReconstructionStencilTopologyData< dim, n_components >, n_faces > boundary_stencils
Definition KurganovTadmor.hh:719
std::array< FaceJacobianDependencyCacheEntry, n_faces > face_jacobian_dependencies
Definition KurganovTadmor.hh:720
internal::CellStencilTopologyData< dim, n_components > stencil
Definition KurganovTadmor.hh:717
std::vector< NumberType > jxw
Definition KurganovTadmor.hh:723
std::vector< Point > quadrature_points
Definition KurganovTadmor.hh:722
std::vector< types::global_dof_index > from_dofs
Definition KurganovTadmor.hh:706
std::vector< types::global_dof_index > to_dofs
Definition KurganovTadmor.hh:705
unsigned int cell_index
Definition KurganovTadmor.hh:709
std::optional< unsigned int > neighbor_index
Definition KurganovTadmor.hh:712
unsigned int face_index
Definition KurganovTadmor.hh:710
std::optional< unsigned int > neighbor_face_index
Definition KurganovTadmor.hh:713
GradientType gradients
Definition KurganovTadmor.hh:877
std::array< Tensor< 2, dim, NumberType >, n_components > hessians
Definition KurganovTadmor.hh:878
std::array< NumberType, n_components > values
Definition KurganovTadmor.hh:876
Definition reconstruction_cache.hh:325
bool lower_boundary
Definition reconstruction_cache.hh:330
bool lower_boundary
Definition reconstruction_cache.hh:342
std::array< std::array< dealii::types::global_dof_index, n_components >, stencil_size > dof_indices
Definition reconstruction_cache.hh:341
std::array< dealii::Point< dim >, stencil_size > x
Definition reconstruction_cache.hh:340
size_t ghost_center
Definition reconstruction_cache.hh:345
unsigned int cell_face
Definition reconstruction_cache.hh:343
size_t ghost_left
Definition reconstruction_cache.hh:346
dealii::Point< dim > face_center
Definition reconstruction_cache.hh:344
size_t ghost_right
Definition reconstruction_cache.hh:347
Definition reconstruction_cache.hh:94
std::array< NumberType, n_components > u
Definition reconstruction_cache.hh:96
std::array< dealii::types::global_dof_index, n_components > dof_indices
Definition reconstruction_cache.hh:97
dealii::Point< dim > x
Definition reconstruction_cache.hh:95
Definition reconstruction_cache.hh:100
Definition reconstruction_cache.hh:118
NeighborData< dim, NumberType, n_components > neighbors
Definition reconstruction_cache.hh:121
CellData< dim, NumberType, n_components > cell
Definition reconstruction_cache.hh:120
std::array< dealii::types::boundary_id, n_faces > boundary_ids
Definition reconstruction_cache.hh:122
std::array< dealii::Point< dim >, n_faces > face_centers
Definition reconstruction_cache.hh:123
std::array< double, 2 > values
Definition KurganovTadmor.hh:189
std::array< uint, 2 > cell_indices
Definition KurganovTadmor.hh:188
Definition KurganovTadmor.hh:186
std::vector< CopyFaceData_I > face_data
Definition KurganovTadmor.hh:191
uint cell_index
Definition KurganovTadmor.hh:193
double value
Definition KurganovTadmor.hh:192
std::vector< types::global_dof_index > from_dofs
Definition KurganovTadmor.hh:149
std::vector< types::global_dof_index > to_dofs
Definition KurganovTadmor.hh:148
FullMatrix< NumberType > cell_jacobian
Definition KurganovTadmor.hh:146
void reinit(const std::vector< types::global_dof_index > &cached_to_dofs, const std::vector< types::global_dof_index > &cached_from_dofs)
Definition KurganovTadmor.hh:151
FullMatrix< NumberType > extractor_cell_jacobian
Definition KurganovTadmor.hh:147
Definition KurganovTadmor.hh:140
FullMatrix< NumberType > extractor_cell_jacobian
Definition KurganovTadmor.hh:161
FullMatrix< NumberType > cell_mass_jacobian
Definition KurganovTadmor.hh:162
typename DoFHandler< dim >::active_cell_iterator Iterator
Definition KurganovTadmor.hh:141
CopyDataFace_J & next_face_data()
Definition KurganovTadmor.hh:179
void reinit(const Iterator &cell, uint dofs_per_cell, uint n_extractors)
Definition KurganovTadmor.hh:167
std::vector< CopyDataFace_J > face_data
Definition KurganovTadmor.hh:164
unsigned int active_face_count
Definition KurganovTadmor.hh:165
std::vector< types::global_dof_index > local_dof_indices
Definition KurganovTadmor.hh:163
FullMatrix< NumberType > cell_jacobian
Definition KurganovTadmor.hh:160
Vector< NumberType > cell_residual
Definition KurganovTadmor.hh:106
void reinit(const unsigned int n_face_dofs)
Definition KurganovTadmor.hh:109
std::vector< types::global_dof_index > joint_dof_indices
Definition KurganovTadmor.hh:107
Definition KurganovTadmor.hh:104
CopyDataFace_R & next_face_data()
Definition KurganovTadmor.hh:132
void reinit(const Iterator &cell, uint dofs_per_cell)
Definition KurganovTadmor.hh:122
Vector< NumberType > cell_mass
Definition KurganovTadmor.hh:117
unsigned int active_face_count
Definition KurganovTadmor.hh:120
std::vector< CopyDataFace_R > face_data
Definition KurganovTadmor.hh:119
std::vector< types::global_dof_index > local_dof_indices
Definition KurganovTadmor.hh:118
Vector< NumberType > cell_residual
Definition KurganovTadmor.hh:116
std::array< SimpleMatrix< dealii::Tensor< 1, dim, dealii::Tensor< 3, dim, NumberType > >, n_components >, 2 > third_derivatives
Definition KurganovTadmor.hh:515
std::array< SimpleMatrix< dealii::Tensor< 1, dim, NumberType >, n_components >, 2 > u
Definition KurganovTadmor.hh:512
std::array< SimpleMatrix< dealii::Tensor< 1, dim, dealii::Tensor< 1, dim, NumberType > >, n_components >, 2 > grad
Definition KurganovTadmor.hh:513
Result struct for compute_kt_flux_and_speeds.
Definition KurganovTadmor.hh:272
std::array< dealii::Tensor< 1, dim, NumberType >, n_components > F_minus
Definition KurganovTadmor.hh:274
std::array< dealii::Tensor< 1, dim, NumberType >, n_components > a_half
Definition KurganovTadmor.hh:275
std::array< dealii::Tensor< 1, dim, NumberType >, n_components > F_plus
Definition KurganovTadmor.hh:273
std::array< SimpleMatrix< dealii::Tensor< 1, dim, NumberType >, n_components >, 2 > u
Definition KurganovTadmor.hh:394
std::array< SimpleMatrix< dealii::Tensor< 1, dim, dealii::Tensor< 1, dim, NumberType > >, n_components >, 2 > grad
Definition KurganovTadmor.hh:395
Definition reconstruction_cache.hh:105
Class to hold data for each assembly thread, i.e. FEValues for cells, interfaces, as well as pre-allo...
Definition KurganovTadmor.hh:72
CellStencilData< dim, NumberType, n_components > temporary_stencil
Definition KurganovTadmor.hh:100
std::vector< types::global_dof_index > ncell_dof_indices
Definition KurganovTadmor.hh:89
std::array< NumberType, n_components > QuadratureValue
Definition KurganovTadmor.hh:73
std::vector< QuadratureValue > solution_values
Definition KurganovTadmor.hh:90
std::array< std::vector< ReconstructionDerivativeData< dim, NumberType, n_components > >, 2 > diffusion_derivatives
Definition KurganovTadmor.hh:94
ScratchData(const dealii::Quadrature< dim > &quadrature)
Definition KurganovTadmor.hh:75
CellStencilData< dim, NumberType, n_components > cell_stencil
Definition KurganovTadmor.hh:98
ScratchData(const ScratchData< dim, NumberType, n_components > &scratch_data)
Definition KurganovTadmor.hh:81
CellStencilData< dim, NumberType, n_components > ncell_stencil
Definition KurganovTadmor.hh:99
std::vector< types::global_dof_index > cell_dof_indices
Definition KurganovTadmor.hh:88
std::vector< QuadratureValue > solution_dot_values
Definition KurganovTadmor.hh:91
std::vector< GradientType< dim, NumberType, n_components > > source_gradient_derivatives
Definition KurganovTadmor.hh:97
std::array< std::vector< ReconstructionDerivativeData< dim, NumberType, n_components > >, 2 > reconstructed_derivatives
Definition KurganovTadmor.hh:93
std::vector< std::array< bool, n_faces > > face_reconstruction_valid
Definition reconstruction_cache.hh:131
std::vector< CellStencilData< dim, NumberType, n_components > > cell_stencils
Definition reconstruction_cache.hh:128
bool topology_initialized
Definition reconstruction_cache.hh:132
std::vector< std::array< FaceReconstructionState< dim, NumberType, n_components >, n_faces > > face_reconstructions
Definition reconstruction_cache.hh:130
Definition tuples.hh:34
Definition run_reporter.hh:73
SummaryEvent & timing(const std::string_view name, const double average_ms, const std::size_t calls)
Definition run_reporter.hh:78
Stand-in for a raw potential that a model has declared it does not read.
Definition eom.hh:140
std::conditional_t< CarriesModel< D >, typename D::Model, D > type
Definition types.hh:63
A class to store a tuple with elements that can be accessed by name. The names are stored as FixedStr...
Definition tuples.hh:56