/home/runner/work/DiFfRG_current/DiFfRG_current/DiFfRG/include/DiFfRG/discretization/common/eom.hh Source File#

DiFfRG: /home/runner/work/DiFfRG_current/DiFfRG_current/DiFfRG/include/DiFfRG/discretization/common/eom.hh Source File
DiFfRG
Discretization Framework for functional Renormalization Group flows
eom.hh
Go to the documentation of this file.
1#pragma once
2
3// external libraries
4#include <boost/signals2/connection.hpp>
5
6#include <Eigen/Eigenvalues>
7#include <Eigen/QR>
8
9#include <deal.II/base/point.h>
10#include <deal.II/distributed/tria_base.h>
11#include <deal.II/base/quadrature_lib.h>
12#include <deal.II/dofs/dof_handler.h>
13#include <deal.II/dofs/dof_tools.h>
14#include <deal.II/fe/fe_dgq.h>
15#include <deal.II/fe/fe_interface_values.h>
16#include <deal.II/fe/fe_q.h>
17#include <deal.II/fe/fe_values.h>
18#include <deal.II/grid/grid_tools.h>
19#include <deal.II/grid/grid_tools_geometry.h>
20#include <deal.II/lac/affine_constraints.h>
21#include <deal.II/lac/dynamic_sparsity_pattern.h>
22#include <deal.II/lac/full_matrix.h>
23#include <deal.II/lac/sparse_direct.h>
24#include <deal.II/lac/sparse_matrix.h>
25#include <deal.II/lac/sparsity_pattern.h>
26#include <deal.II/lac/vector.h>
27#include <deal.II/meshworker/mesh_loop.h>
28#include <deal.II/numerics/fe_field_function.h>
29
34
35// standard library
36#include <algorithm>
37#include <array>
38#include <cmath>
39#include <deque>
40#include <limits>
41#include <map>
42#include <memory>
43#include <mutex>
44#include <optional>
45#include <stdexcept>
46#include <utility>
47#include <vector>
48
49namespace DiFfRG
50{
51 namespace internal
52 {
66 std::size_t solves = 0;
67 double seconds = 0.;
68 };
69
71 {
72 static thread_local PotentialSolveStats stats;
73 return stats;
74 }
75 } // namespace internal
76
83 template <int dim, typename NumberType> struct ReconstructedEoMPotential {
84 dealii::Point<dim> minimum;
85 std::shared_ptr<dealii::FiniteElement<dim>> finite_element;
86 std::shared_ptr<dealii::DoFHandler<dim>> dof_handler;
87 dealii::Vector<NumberType> values;
88 };
89
100 template <int dim, typename NumberType> struct RecoveredMassHessian {
101 std::unique_ptr<dealii::FiniteElement<dim>> finite_element;
102 std::unique_ptr<dealii::DoFHandler<dim>> dof_handler;
103 std::array<dealii::Vector<NumberType>, dim * dim> values;
104 std::vector<dealii::Point<dim>> sample_centers;
105 std::vector<dealii::Tensor<2, dim, NumberType>> samples;
106 std::vector<std::vector<uint>> neighbors;
107 std::vector<std::vector<double>> face_weights;
108 std::vector<std::vector<uint>> kink_faces_by_cell;
109 std::vector<dealii::Point<dim>> kink_face_centers;
110 std::vector<dealii::Tensor<1, dim>> kink_face_normals;
111 std::vector<double> kink_face_widths;
112 std::vector<double> kink_face_strengths;
113 };
114
115 template <int dim, typename NumberType> struct ReconstructedRawPotential {
116 std::shared_ptr<dealii::FiniteElement<dim>> finite_element;
117 std::shared_ptr<dealii::DoFHandler<dim>> dof_handler;
118 dealii::Vector<NumberType> values;
119 std::unique_ptr<RecoveredMassHessian<dim, NumberType>> recovered_mass_hessian;
120 };
121
122 template <typename NumberType> struct ReconstructedRawPotential<0, NumberType> {
123 dealii::Vector<NumberType> values;
124 };
125
127 template <int dim, typename NumberType> struct RawPotentialEvaluation {
128 NumberType value{};
129 dealii::Tensor<1, dim, NumberType> gradient;
130 dealii::Tensor<2, dim, NumberType> potential_hessian;
131 dealii::Tensor<2, dim, NumberType> mass_hessian;
132 };
133
141 };
142
147
148 template <int dim>
150 const dealii::Point<dim> &)
151 {
152 return {};
153 }
154
155 template <typename NumberType> struct ReconstructedEoMPotential<0, NumberType> {
156 dealii::Point<0> minimum;
157 dealii::Vector<NumberType> values;
158 };
159
163 template <int dim, typename NumberType> struct EoMResult {
164 dealii::Point<dim> point;
165 std::optional<ReconstructedEoMPotential<dim, NumberType>> potential;
166 };
167
168 namespace internal
169 {
170 using namespace dealii;
171
172 template <int dim>
173 dealii::Point<dim> get_origin(const dealii::DoFHandler<dim> &dof_handler,
174 typename dealii::DoFHandler<dim>::cell_iterator &EoM_cell)
175 {
176 using CellIterator = typename dealii::DoFHandler<dim>::cell_iterator;
177
178 auto l1_norm = [](const Point<dim> &p) {
179 double norm = 0.;
180 for (uint d = 0; d < dim; ++d)
181 norm += std::abs(p[d]);
182 return norm;
183 };
184
185 std::vector<Point<dim>> candidates;
186
187 auto iterate_cell = [&](const CellIterator &cell) {
188 std::array<Point<dim>, GeometryInfo<dim>::vertices_per_cell> vertices;
189 for (uint i = 0; i < GeometryInfo<dim>::vertices_per_cell; ++i)
190 vertices[i] = cell->vertex(i);
191
192 auto min_it = std::min_element(vertices.begin(), vertices.end(),
193 [&](const auto &p1, const auto &p2) { return l1_norm(p1) < l1_norm(p2); });
194 candidates.push_back(*min_it);
195 };
196
197 for (const auto &cell : dof_handler.active_cell_iterators())
198 iterate_cell(cell);
199
200 auto min_it = std::min_element(candidates.begin(), candidates.end(),
201 [&](const auto &p1, const auto &p2) { return l1_norm(p1) < l1_norm(p2); });
202
203 Point<dim> origin = *min_it;
204 EoM_cell = GridTools::find_active_cell_around_point(dof_handler, origin);
205
206 return origin;
207 }
208
217 template <int dim>
218 typename dealii::DoFHandler<dim>::active_cell_iterator
219 matching_dof_cell(const dealii::DoFHandler<dim> &target_dof_handler,
220 const typename dealii::DoFHandler<dim>::active_cell_iterator &source_cell)
221 {
222 return typename dealii::DoFHandler<dim>::active_cell_iterator(
223 &target_dof_handler.get_triangulation(), source_cell->level(), source_cell->index(), &target_dof_handler);
224 }
225
226 template <int dim> double l1_distance(const dealii::Point<dim> &a, const dealii::Point<dim> &b)
227 {
228 double distance = 0.;
229 for (uint d = 0; d < dim; ++d)
230 distance += std::abs(a[d] - b[d]);
231 return distance;
232 }
233
234 template <int dim>
235 double resolve_potential_smoothing_length(const dealii::DoFHandler<dim> &dof_handler,
236 const double configured_length)
237 {
238 if (!std::isfinite(configured_length) || configured_length < -1. ||
239 (configured_length < 0. && configured_length != -1.))
240 throw std::invalid_argument(
241 "EoM_smoothing_length must be -1 (automatic), zero, or a positive physical length.");
242
243 if (configured_length == -1.) return 2. * minimum_face_normal_cell_width(dof_handler);
244 return configured_length;
245 }
246
247 template <int dim>
248 Config::EoMConfig resolve_eom_config(const dealii::DoFHandler<dim> &dof_handler, Config::EoMConfig config)
249 {
251 config.smoothing_length = resolve_potential_smoothing_length(dof_handler, config.smoothing_length);
252 return config;
253 }
254
255 template <int dim, typename NumberType>
256 dealii::types::global_dof_index
257 select_gauge_dof(const dealii::AffineConstraints<NumberType> &constraints, const dealii::Point<dim> &origin,
258 const std::map<dealii::types::global_dof_index, dealii::Point<dim>> &support_points)
259 {
260 dealii::types::global_dof_index best_dof = numbers::invalid_dof_index;
261 double best_distance = std::numeric_limits<double>::max();
262 for (const auto &dof_and_point : support_points) {
263 const auto dof = dof_and_point.first;
264 if (constraints.is_constrained(dof)) continue;
265
266 const double distance = l1_distance(dof_and_point.second, origin);
267 if (distance < best_distance) {
268 best_distance = distance;
269 best_dof = dof;
270 }
271 }
272
273 if (best_dof == numbers::invalid_dof_index)
274 throw std::runtime_error("EoM potential reconstruction could not find an unconstrained gauge DoF.");
275
276 return best_dof;
277 }
278
279 template <int dim> std::unique_ptr<dealii::FiniteElement<dim>> make_eom_potential_fe()
280 {
281 return std::make_unique<FE_Q<dim>>(2);
282 }
283
284 template <int dim> std::unique_ptr<dealii::FiniteElement<dim>> make_raw_potential_fe(const uint order = 2)
285 {
286 return std::make_unique<FE_Q<dim>>(order);
287 }
288
290 enum class PotentialKind : uint { eom = 0, raw = 1 };
291 inline constexpr uint n_potential_kinds = 2;
292
293 template <int dim>
294 std::unique_ptr<dealii::FiniteElement<dim>> make_potential_fe(const PotentialKind kind, const uint raw_order)
295 {
297 }
298
314 template <int dim, typename NumberType> class PotentialSystemCache
315 {
316 static constexpr int face_dim = dim > 0 ? dim - 1 : 0;
317
318 public:
319 struct Entry {
320 std::shared_ptr<dealii::FiniteElement<dim>> finite_element;
321 std::shared_ptr<dealii::DoFHandler<dim>> dof_handler;
322 dealii::AffineConstraints<NumberType> constraints;
323 dealii::SparsityPattern sparsity_pattern;
324 dealii::SparseMatrix<NumberType> matrix;
325 dealii::SparseDirectUMFPACK factorization;
326 dealii::Quadrature<dim> quadrature;
327 dealii::Quadrature<face_dim> face_quadrature;
329 std::map<dealii::types::global_dof_index, dealii::Point<dim>> support_points;
330
339 bool describes(const dealii::Triangulation<dim> &tria, const dealii::DoFHandler<dim> &solution_dof_handler,
340 const double smoothing, const uint potential_fe_degree) const
341 {
342 return !stale && connection.connected() && triangulation == &tria &&
343 solution_dofs == &solution_dof_handler && n_active_cells == tria.n_active_cells() &&
344 n_levels == tria.n_levels() && n_vertices == tria.n_vertices() &&
345 solution_fe_degree == solution_dof_handler.get_fe().degree && smoothing_length == smoothing &&
346 finite_element != nullptr && finite_element->degree == potential_fe_degree;
347 }
348
349 void note_built_for(const dealii::Triangulation<dim> &tria,
350 const dealii::DoFHandler<dim> &solution_dof_handler, const double smoothing)
351 {
352 triangulation = &tria;
353 solution_dofs = &solution_dof_handler;
354 n_active_cells = tria.n_active_cells();
355 n_levels = tria.n_levels();
356 n_vertices = tria.n_vertices();
357 solution_fe_degree = solution_dof_handler.get_fe().degree;
358 smoothing_length = smoothing;
359 connection = tria.signals.any_change.connect([this]() { stale = true; });
360 stale = false;
361 }
362
363 private:
364 const void *triangulation = nullptr;
365 const void *solution_dofs = nullptr;
366 std::size_t n_active_cells = 0;
368 std::size_t n_vertices = 0;
370 double smoothing_length = 0.;
371 boost::signals2::scoped_connection connection;
372 bool stale = true;
373 };
374
382 {
383 auto &slot = entries[static_cast<uint>(kind)];
384 if (!slot) slot = std::make_unique<Entry>();
385 return *slot;
386 }
387
388 private:
389 std::array<std::unique_ptr<Entry>, n_potential_kinds> entries;
390 };
391
392 template <int dim, typename EoMValue> dealii::Tensor<1, dim> eom_to_tensor(const EoMValue &eom)
393 {
394 dealii::Tensor<1, dim> out;
395 for (uint d = 0; d < dim; ++d)
396 out[d] = eom[d];
397 return out;
398 }
399
400 template <int dim, typename NumberType> struct DG0GradientModel {
401 dealii::Point<dim> center;
402 dealii::Tensor<1, dim, NumberType> value;
403 dealii::Tensor<2, dim, NumberType> jacobian;
404 };
405
406 template <int dim, typename VectorType, typename GradientFUN>
407 std::vector<DG0GradientModel<dim, typename VectorType::value_type>>
408 recover_dg0_gradient_models(const VectorType &sol, const dealii::DoFHandler<dim> &solution_dof_handler,
409 const dealii::Mapping<dim> &mapping, const GradientFUN &get_gradient)
410 {
411 using NumberType = typename VectorType::value_type;
412 using Iterator = typename dealii::DoFHandler<dim>::active_cell_iterator;
413
414 const uint n_cells = solution_dof_handler.get_triangulation().n_active_cells();
415 std::vector<Iterator> cells(n_cells);
416 std::vector<DG0GradientModel<dim, NumberType>> models(n_cells);
417
418 dealii::QMidpoint<dim> midpoint;
419 dealii::FEValues<dim> values(mapping, solution_dof_handler.get_fe(), midpoint,
420 dealii::update_values | dealii::update_quadrature_points);
421 std::vector<dealii::Vector<NumberType>> solution_values(
422 1, dealii::Vector<NumberType>(solution_dof_handler.get_fe().n_components()));
423 for (const auto &cell : solution_dof_handler.active_cell_iterators()) {
424 const uint index = cell->active_cell_index();
425 cells[index] = cell;
426 values.reinit(cell);
427 values.get_function_values(sol, solution_values);
428 models[index].center = values.quadrature_point(0);
429 models[index].value = eom_to_tensor<dim>(get_gradient(models[index].center, solution_values[0]));
430 }
431
432 const uint target_patch_size = std::min<uint>(n_cells, 2 * (dim + 1));
433 for (uint cell_index = 0; cell_index < n_cells; ++cell_index) {
434 std::vector<uint> patch{cell_index};
435 std::vector<uint> frontier{cell_index};
436 std::vector<bool> seen(n_cells, false);
437 seen[cell_index] = true;
438
439 while (patch.size() < target_patch_size && !frontier.empty()) {
440 std::vector<uint> next_frontier;
441 for (const uint index : frontier) {
442 std::vector<Iterator> neighbors;
443 dealii::GridTools::get_active_neighbors<dealii::DoFHandler<dim>>(cells[index], neighbors);
444 for (const auto &neighbor : neighbors) {
445 const uint neighbor_index = neighbor->active_cell_index();
446 if (seen[neighbor_index]) continue;
447 seen[neighbor_index] = true;
448 patch.push_back(neighbor_index);
449 next_frontier.push_back(neighbor_index);
450 }
451 }
452 frontier = std::move(next_frontier);
453 }
454
455 if (patch.size() < dim + 1) continue;
456 const double scale = std::max(cells[cell_index]->diameter(), std::numeric_limits<double>::epsilon());
457 Eigen::MatrixXd design(patch.size(), dim + 1);
458 for (uint row = 0; row < patch.size(); ++row) {
459 design(row, 0) = 1.;
460 for (uint d = 0; d < dim; ++d)
461 design(row, d + 1) = (models[patch[row]].center[d] - models[cell_index].center[d]) / scale;
462 }
463
464 Eigen::ColPivHouseholderQR<Eigen::MatrixXd> qr(design);
465 if (qr.rank() < dim + 1) continue;
466 for (uint component = 0; component < dim; ++component) {
467 Eigen::VectorXd samples(patch.size());
468 for (uint row = 0; row < patch.size(); ++row)
469 samples[row] = models[patch[row]].value[component];
470 const Eigen::VectorXd coefficients = qr.solve(samples);
471 for (uint d = 0; d < dim; ++d)
472 models[cell_index].jacobian[component][d] = coefficients[d + 1] / scale;
473 }
474 }
475
476 return models;
477 }
478
479 template <int dim, typename NumberType>
480 dealii::Tensor<1, dim, NumberType> evaluate_dg0_gradient_model(const DG0GradientModel<dim, NumberType> &model,
481 const dealii::Point<dim> &point)
482 {
483 auto value = model.value;
484 for (uint component = 0; component < dim; ++component)
485 for (uint d = 0; d < dim; ++d)
486 value[component] += model.jacobian[component][d] * (point[d] - model.center[d]);
487 return value;
488 }
489
490 template <int dim, typename NumberType>
491 dealii::Tensor<2, dim, NumberType> symmetric_jacobian(const DG0GradientModel<dim, NumberType> &model)
492 {
493 dealii::Tensor<2, dim, NumberType> hessian;
494 for (uint d = 0; d < dim; ++d)
495 for (uint e = 0; e < dim; ++e)
496 hessian[d][e] = 0.5 * (model.jacobian[d][e] + model.jacobian[e][d]);
497 return hessian;
498 }
499
500 template <int dim> struct HessianJumpCompatibility {
501 double relative_jump = 0.;
503 bool is_rank_one = false;
504 dealii::Tensor<1, dim> normal;
505 };
506
507 inline constexpr double minimum_rank_one_confidence = 0.5;
508
510 template <int dim, typename NumberType>
511 HessianJumpCompatibility<dim> hessian_jump_compatibility(const dealii::Tensor<2, dim, NumberType> &left,
512 const dealii::Tensor<2, dim, NumberType> &right,
513 const dealii::Tensor<1, dim> &coordinate_scale)
514 {
515 Eigen::Matrix<double, dim, dim> jump;
516 Eigen::Matrix<double, dim, dim> average;
517 for (uint d = 0; d < dim; ++d)
518 for (uint e = 0; e < dim; ++e) {
519 jump(d, e) = coordinate_scale[d] * static_cast<double>(right[d][e] - left[d][e]) * coordinate_scale[e];
520 average(d, e) =
521 0.5 * coordinate_scale[d] * static_cast<double>(right[d][e] + left[d][e]) * coordinate_scale[e];
522 }
523
524 Eigen::SelfAdjointEigenSolver<Eigen::Matrix<double, dim, dim>> eigensolver(jump);
525 if (eigensolver.info() != Eigen::Success) return {};
526
527 uint leading = 0;
528 double leading_value = 0.;
529 double remainder_square = 0.;
530 for (uint i = 0; i < dim; ++i) {
531 const double value = std::abs(eigensolver.eigenvalues()[i]);
532 if (value > leading_value) {
533 remainder_square += leading_value * leading_value;
534 leading_value = value;
535 leading = i;
536 } else {
537 remainder_square += value * value;
538 }
539 }
540
542 const double epsilon = std::numeric_limits<double>::epsilon();
543 result.relative_jump = jump.norm() / std::max(average.norm(), epsilon);
544 result.rank_one_confidence =
545 std::clamp(1. - std::sqrt(remainder_square) / std::max(leading_value, epsilon), 0., 1.);
546 result.is_rank_one = leading_value > epsilon && result.rank_one_confidence >= minimum_rank_one_confidence;
547 for (uint d = 0; d < dim; ++d)
548 result.normal[d] = eigensolver.eigenvectors()(d, leading) / std::max(coordinate_scale[d], epsilon);
549 const double normal_norm = result.normal.norm();
550 if (normal_norm > 0.) result.normal /= normal_norm;
551 return result;
552 }
553
554 template <int dim, typename NumberType>
556 const std::vector<DG0GradientModel<dim, NumberType>> &models,
557 const dealii::DoFHandler<dim> &solution_dof_handler,
558 const double jump_threshold)
559 {
560 using Iterator = typename dealii::DoFHandler<dim>::active_cell_iterator;
561 const uint n_cells = models.size();
562 std::vector<Iterator> cells(n_cells);
563 std::vector<dealii::Tensor<1, dim>> coordinate_scales(n_cells);
564 auto &recovery = *potential.recovered_mass_hessian;
565
566 recovery.sample_centers.resize(n_cells);
567 recovery.samples.resize(n_cells);
568 recovery.neighbors.resize(n_cells);
569 recovery.face_weights.resize(n_cells);
570 recovery.kink_faces_by_cell.resize(n_cells);
571
572 for (const auto &cell : solution_dof_handler.active_cell_iterators()) {
573 const uint index = cell->active_cell_index();
574 cells[index] = cell;
575 recovery.sample_centers[index] = models[index].center;
576 recovery.samples[index] = symmetric_jacobian(models[index]);
577 for (uint d = 0; d < dim; ++d) {
578 double lower = std::numeric_limits<double>::max();
579 double upper = -std::numeric_limits<double>::max();
580 for (uint vertex = 0; vertex < cell->n_vertices(); ++vertex) {
581 lower = std::min(lower, cell->vertex(vertex)[d]);
582 upper = std::max(upper, cell->vertex(vertex)[d]);
583 }
584 coordinate_scales[index][d] = std::max(upper - lower, std::numeric_limits<double>::epsilon());
585 }
586 }
587
588 for (uint index = 0; index < n_cells; ++index) {
589 std::vector<Iterator> neighbors;
590 dealii::GridTools::get_active_neighbors<dealii::DoFHandler<dim>>(cells[index], neighbors);
591 for (const auto &neighbor : neighbors) {
592 const uint neighbor_index = neighbor->active_cell_index();
593 dealii::Tensor<1, dim> scale;
594 for (uint d = 0; d < dim; ++d)
595 scale[d] = 0.5 * (coordinate_scales[index][d] + coordinate_scales[neighbor_index][d]);
596 const auto compatibility =
597 hessian_jump_compatibility(recovery.samples[index], recovery.samples[neighbor_index], scale);
598 const double strength =
599 compatibility.is_rank_one ? compatibility.relative_jump * compatibility.rank_one_confidence : 0.;
600 const double ratio = strength / std::max(jump_threshold, std::numeric_limits<double>::epsilon());
601 const double face_weight = jump_threshold <= 0. ? (strength > 0. ? 0. : 1.) : 1. / (1. + std::pow(ratio, 6.));
602 recovery.neighbors[index].push_back(neighbor_index);
603 recovery.face_weights[index].push_back(face_weight);
604 if (index < neighbor_index && face_weight < 1. - 1e-8) {
605 const auto separation = recovery.sample_centers[neighbor_index] - recovery.sample_centers[index];
606 const double width = separation.norm();
607 if (width > 0.) {
608 const uint face_index = recovery.kink_face_centers.size();
609 recovery.kink_face_centers.push_back(
610 0.5 * (recovery.sample_centers[index] + recovery.sample_centers[neighbor_index]));
611 recovery.kink_face_normals.push_back(compatibility.normal);
612 recovery.kink_face_widths.push_back(width);
613 recovery.kink_face_strengths.push_back(1. - face_weight);
614 recovery.kink_faces_by_cell[index].push_back(face_index);
615 recovery.kink_faces_by_cell[neighbor_index].push_back(face_index);
616 }
617 }
618 }
619 }
620 }
621
622 template <int dim> struct EoMSideHessianBlend {
623 double weight = 0.;
624 dealii::Tensor<1, dim> normal;
625 };
626
628 template <int dim, typename NumberType>
630 const uint root_cell, const dealii::Point<dim> &point)
631 {
633 if (!potential.recovered_mass_hessian) return result;
634 const auto &recovery = *potential.recovered_mass_hessian;
635 if (root_cell >= recovery.kink_faces_by_cell.size()) return result;
636 for (const uint face : recovery.kink_faces_by_cell[root_cell]) {
637 const double width = recovery.kink_face_widths[face];
638 const double distance = point.distance(recovery.kink_face_centers[face]);
639 const double radius = 1.5 * width;
640 const double weight =
641 recovery.kink_face_strengths[face] * std::exp(-0.5 * distance * distance / (radius * radius));
642 if (weight > result.weight) {
643 result.weight = weight;
644 result.normal = recovery.kink_face_normals[face];
645 }
646 }
647 result.weight = std::clamp(result.weight, 0., 1.);
648 return result;
649 }
650
657 template <int dim, typename NumberType>
659 const std::vector<DG0GradientModel<dim, NumberType>> &models,
660 const dealii::DoFHandler<dim> &solution_dof_handler)
661 {
662 potential.recovered_mass_hessian = std::make_unique<RecoveredMassHessian<dim, NumberType>>();
663 auto &recovery = *potential.recovered_mass_hessian;
664 recovery.finite_element = std::make_unique<dealii::FE_Q<dim>>(1);
665 recovery.dof_handler = std::make_unique<dealii::DoFHandler<dim>>(solution_dof_handler.get_triangulation());
666 recovery.dof_handler->distribute_dofs(*recovery.finite_element);
667
668 const auto n_dofs = recovery.dof_handler->n_dofs();
669 std::vector<std::vector<uint>> incident_cells(n_dofs);
670 std::vector<dealii::types::global_dof_index> local_dof_indices(recovery.finite_element->n_dofs_per_cell());
671 for (const auto &cell : recovery.dof_handler->active_cell_iterators()) {
672 cell->get_dof_indices(local_dof_indices);
673 for (const auto dof : local_dof_indices)
674 incident_cells[dof].push_back(cell->active_cell_index());
675 }
676
677 for (auto &values : recovery.values)
678 values.reinit(n_dofs);
679
680 for (dealii::types::global_dof_index dof = 0; dof < n_dofs; ++dof) {
681 const auto &cells = incident_cells[dof];
682 if (cells.empty()) continue;
683
684 for (uint derivative_direction = 0; derivative_direction < dim; ++derivative_direction) {
685 dealii::Tensor<1, dim, NumberType> mean_trace;
686
687 for (const auto cell_index : cells) {
688 const auto hessian = symmetric_jacobian(models[cell_index]);
689 dealii::Tensor<1, dim, NumberType> trace;
690 for (uint component = 0; component < dim; ++component)
691 trace[component] = hessian[component][derivative_direction];
692 mean_trace += trace;
693 }
694 mean_trace /= static_cast<double>(cells.size());
695 for (uint component = 0; component < dim; ++component)
696 recovery.values[component * dim + derivative_direction][dof] = mean_trace[component];
697 }
698 }
699
700 dealii::AffineConstraints<NumberType> constraints;
701 dealii::DoFTools::make_hanging_node_constraints(*recovery.dof_handler, constraints);
702 constraints.close();
703 for (auto &values : recovery.values)
704 constraints.distribute(values);
705 }
706
708 template <int dim, typename NumberType>
709 std::optional<dealii::Tensor<2, dim, NumberType>>
711 const dealii::Point<dim> &point)
712 {
713 if (!potential.recovered_mass_hessian) return std::nullopt;
714 const auto &recovery = *potential.recovered_mass_hessian;
715 if (root_cell >= recovery.neighbors.size()) return std::nullopt;
716
717 struct ConnectedCell {
718 uint index;
719 uint depth;
720 double connectivity;
721 };
722 std::vector<ConnectedCell> connected_cells{{root_cell, 0, 1.}};
723 std::deque<uint> frontier{0};
724 constexpr uint max_depth = 3;
725 while (!frontier.empty()) {
726 const uint connected_index = frontier.front();
727 frontier.pop_front();
728 const auto current = connected_cells[connected_index];
729 if (current.depth == max_depth) continue;
730 for (uint edge = 0; edge < recovery.neighbors[current.index].size(); ++edge) {
731 const uint neighbor = recovery.neighbors[current.index][edge];
732 const double candidate = current.connectivity * recovery.face_weights[current.index][edge];
733 const auto existing = std::find_if(connected_cells.begin(), connected_cells.end(),
734 [neighbor](const auto &cell) { return cell.index == neighbor; });
735 if (existing != connected_cells.end()) {
736 if (candidate <= existing->connectivity + 1e-12) continue;
737 existing->depth = current.depth + 1;
738 existing->connectivity = candidate;
739 frontier.push_back(std::distance(connected_cells.begin(), existing));
740 } else if (candidate >= 1e-6) {
741 connected_cells.push_back({neighbor, current.depth + 1, candidate});
742 frontier.push_back(connected_cells.size() - 1);
743 }
744 }
745 }
746
747 double nearest_distance = std::numeric_limits<double>::max();
748 for (const uint neighbor : recovery.neighbors[root_cell])
749 nearest_distance =
750 std::min(nearest_distance, recovery.sample_centers[root_cell].distance(recovery.sample_centers[neighbor]));
751 if (!std::isfinite(nearest_distance)) return std::nullopt;
752 const double fit_radius = 1.5 * nearest_distance;
753 dealii::Tensor<2, dim, NumberType> hessian;
754 double weight_sum = 0.;
755 for (const auto &cell : connected_cells) {
756 const double distance = point.distance(recovery.sample_centers[cell.index]);
757 const double spatial_weight = std::exp(-0.5 * distance * distance / (fit_radius * fit_radius));
758 const double weight = cell.connectivity * spatial_weight;
759 if (weight < 1e-8) continue;
760 hessian += weight * recovery.samples[cell.index];
761 weight_sum += weight;
762 }
763 if (!(weight_sum > 0.)) return std::nullopt;
764 hessian /= weight_sum;
765 return hessian;
766 }
767
768 template <int dim, typename NumberType> struct PotentialAssemblyScratch {
769 PotentialAssemblyScratch(const dealii::Mapping<dim> &mapping, const dealii::FiniteElement<dim> &solution_fe,
770 const dealii::FiniteElement<dim> &potential_fe,
771 const dealii::Quadrature<dim> &quadrature,
772 const dealii::Quadrature<dim - 1> &face_quadrature)
773 : solution_fe_values(mapping, solution_fe, quadrature, update_values | update_quadrature_points),
774 potential_fe_values(mapping, potential_fe, quadrature,
775 update_gradients | update_JxW_values | update_quadrature_points),
776 solution_fe_interface_values(mapping, solution_fe, face_quadrature,
777 update_values | update_quadrature_points),
778 potential_fe_interface_values(mapping, potential_fe, face_quadrature,
779 update_values | update_gradients | update_JxW_values |
780 update_quadrature_points | update_normal_vectors)
781 {
782 solution_values.resize(quadrature.size(), Vector<NumberType>(solution_fe.n_components()));
783 solution_interface_values[0].resize(face_quadrature.size(), Vector<NumberType>(solution_fe.n_components()));
784 solution_interface_values[1].resize(face_quadrature.size(), Vector<NumberType>(solution_fe.n_components()));
785 }
786
788 : solution_fe_values(scratch.solution_fe_values.get_mapping(), scratch.solution_fe_values.get_fe(),
789 scratch.solution_fe_values.get_quadrature(),
790 scratch.solution_fe_values.get_update_flags()),
791 potential_fe_values(scratch.potential_fe_values.get_mapping(), scratch.potential_fe_values.get_fe(),
792 scratch.potential_fe_values.get_quadrature(),
793 scratch.potential_fe_values.get_update_flags()),
795 scratch.solution_fe_interface_values.get_fe(),
796 scratch.solution_fe_interface_values.get_quadrature(),
797 scratch.solution_fe_interface_values.get_update_flags()),
799 scratch.potential_fe_interface_values.get_fe(),
800 scratch.potential_fe_interface_values.get_quadrature(),
801 scratch.potential_fe_interface_values.get_update_flags())
802 {
803 const uint n_components = scratch.solution_fe_values.get_fe().n_components();
804 solution_values.resize(scratch.solution_fe_values.get_quadrature().size(), Vector<NumberType>(n_components));
805 solution_interface_values[0].resize(scratch.solution_fe_interface_values.get_quadrature().size(),
806 Vector<NumberType>(n_components));
807 solution_interface_values[1].resize(scratch.solution_fe_interface_values.get_quadrature().size(),
808 Vector<NumberType>(n_components));
809 }
810
811 FEValues<dim> solution_fe_values;
812 FEValues<dim> potential_fe_values;
813 FEInterfaceValues<dim> solution_fe_interface_values;
814 FEInterfaceValues<dim> potential_fe_interface_values;
815 std::vector<Vector<NumberType>> solution_values;
816 std::array<std::vector<Vector<NumberType>>, 2> solution_interface_values;
817 };
818
819 template <typename NumberType> struct PotentialAssemblyCopy {
820 struct FaceCopy {
821 FullMatrix<NumberType> matrix;
822 Vector<NumberType> rhs;
823 std::vector<types::global_dof_index> dof_indices;
824 };
825
826 template <class Iterator> void reinit_cell(const Iterator &cell, const uint dofs_per_cell)
827 {
828 cell_matrix.reinit(dofs_per_cell, dofs_per_cell);
829 cell_rhs.reinit(dofs_per_cell);
830 cell_dof_indices.resize(dofs_per_cell);
831 cell->get_dof_indices(cell_dof_indices);
832 face_data.clear();
833 face_data.reserve(cell->n_faces());
834 }
835
836 template <int dim> FaceCopy &new_face_data(const FEInterfaceValues<dim> &fe_interface_values)
837 {
838 const uint n_interface_dofs = fe_interface_values.n_current_interface_dofs();
839 face_data.emplace_back();
840 auto &data = face_data.back();
841 data.matrix.reinit(n_interface_dofs, n_interface_dofs);
842 data.rhs.reinit(n_interface_dofs);
843 data.dof_indices = fe_interface_values.get_interface_dof_indices();
844 return data;
845 }
846
847 FullMatrix<NumberType> cell_matrix;
848 Vector<NumberType> cell_rhs;
849 std::vector<types::global_dof_index> cell_dof_indices;
850 std::vector<FaceCopy> face_data;
851 };
852
853 template <int dim, typename VectorType, typename EoMFUN>
854 void assemble_potential_system(const VectorType &sol, const dealii::DoFHandler<dim> &solution_dof_handler,
855 const dealii::DoFHandler<dim> &potential_dof_handler,
856 const dealii::FiniteElement<dim> &potential_fe, const dealii::Mapping<dim> &mapping,
857 const EoMFUN &get_EoM, const dealii::Quadrature<dim> &quadrature,
858 const dealii::Quadrature<dim - 1> &face_quadrature,
859 const dealii::AffineConstraints<typename VectorType::value_type> &constraints,
860 dealii::SparseMatrix<typename VectorType::value_type> &matrix,
861 dealii::Vector<typename VectorType::value_type> &rhs,
862 const double smoothing_length, const bool assemble_matrix)
863 {
864 using NumberType = typename VectorType::value_type;
865 using Iterator = typename dealii::DoFHandler<dim>::active_cell_iterator;
868
869 const FEValuesExtractors::Scalar scalar(0);
870 const bool recover_dg0_gradient = solution_dof_handler.get_fe().degree == 0;
871 const auto dg0_gradient_models = recover_dg0_gradient
872 ? recover_dg0_gradient_models(sol, solution_dof_handler, mapping, get_EoM)
873 : std::vector<DG0GradientModel<dim, NumberType>>{};
874
875 const auto evaluate_gradient = [&](const Iterator &cell, const dealii::Point<dim> &point,
876 const dealii::Vector<NumberType> &values) {
877 if (recover_dg0_gradient)
878 return evaluate_dg0_gradient_model(dg0_gradient_models[cell->active_cell_index()], point);
879 return eom_to_tensor<dim>(get_EoM(point, values));
880 };
881
882 const auto cell_worker = [&](const Iterator &potential_cell, Scratch &scratch, Copy &copy) {
883 const auto solution_cell = matching_dof_cell(solution_dof_handler, potential_cell);
884 const uint dofs_per_cell = potential_fe.n_dofs_per_cell();
885
886 scratch.solution_fe_values.reinit(solution_cell);
887 scratch.potential_fe_values.reinit(potential_cell);
888 scratch.solution_fe_values.get_function_values(sol, scratch.solution_values);
889
890 copy.reinit_cell(potential_cell, dofs_per_cell);
891
892 for (const auto q : scratch.potential_fe_values.quadrature_point_indices()) {
893 const auto eom = evaluate_gradient(solution_cell, scratch.potential_fe_values.quadrature_point(q),
894 scratch.solution_values[q]);
895
896 for (uint i = 0; i < dofs_per_cell; ++i) {
897 const auto grad_i = scratch.potential_fe_values.shape_grad(i, q);
898 copy.cell_rhs(i) += scratch.potential_fe_values.JxW(q) * scalar_product(eom, grad_i);
899
900 if (!assemble_matrix) continue;
901 for (uint j = 0; j < dofs_per_cell; ++j)
902 copy.cell_matrix(i, j) += scratch.potential_fe_values.JxW(q) *
903 scalar_product(scratch.potential_fe_values.shape_grad(j, q), grad_i);
904 }
905 }
906 };
907 const auto boundary_worker = []([[maybe_unused]] const Iterator &cell, [[maybe_unused]] const uint &face_no,
908 [[maybe_unused]] Scratch &scratch, [[maybe_unused]] Copy &copy) {};
909 // Every constraint on the potential is homogeneous -- hanging nodes, plus the gauge DoF pinned to
910 // zero -- so distributing the right-hand side alone needs no local matrix to carry an inhomogeneity.
911 const auto copier = [&](const Copy &copy) {
912 if (assemble_matrix) {
913 constraints.distribute_local_to_global(copy.cell_matrix, copy.cell_rhs, copy.cell_dof_indices, matrix, rhs);
914 for (const auto &face : copy.face_data)
915 constraints.distribute_local_to_global(face.matrix, face.rhs, face.dof_indices, matrix, rhs);
916 } else {
917 constraints.distribute_local_to_global(copy.cell_rhs, copy.cell_dof_indices, rhs);
918 for (const auto &face : copy.face_data)
919 constraints.distribute_local_to_global(face.rhs, face.dof_indices, rhs);
920 }
921 };
922 const auto face_worker = [&](const Iterator &potential_cell, const uint &face_no, const uint &subface_no,
923 const Iterator &potential_neighbor, const uint &neighbor_face_no,
924 const uint &neighbor_subface_no, Scratch &scratch, Copy &copy) {
925 const auto solution_cell = matching_dof_cell(solution_dof_handler, potential_cell);
926 const auto solution_neighbor = matching_dof_cell(solution_dof_handler, potential_neighbor);
927
928 scratch.solution_fe_interface_values.reinit(solution_cell, face_no, subface_no, solution_neighbor,
929 neighbor_face_no, neighbor_subface_no);
930 scratch.potential_fe_interface_values.reinit(potential_cell, face_no, subface_no, potential_neighbor,
931 neighbor_face_no, neighbor_subface_no);
932
933 const auto &solution_fe_interface_values = scratch.solution_fe_interface_values;
934 const auto &potential_fe_interface_values = scratch.potential_fe_interface_values;
935
936 const uint n_interface_dofs = potential_fe_interface_values.n_current_interface_dofs();
937 auto &face_data = copy.new_face_data(potential_fe_interface_values);
938 const auto potential_view = potential_fe_interface_values[scalar];
939 const auto &solution_fe_values_s = solution_fe_interface_values.get_fe_face_values(0);
940 const auto &solution_fe_values_n = solution_fe_interface_values.get_fe_face_values(1);
941 const auto &q_points = potential_fe_interface_values.get_quadrature_points();
942
943 auto &solution_values_s = scratch.solution_interface_values[0];
944 auto &solution_values_n = scratch.solution_interface_values[1];
945 solution_fe_values_s.get_function_values(sol, solution_values_s);
946 solution_fe_values_n.get_function_values(sol, solution_values_n);
947
948 const double h_face = std::min(solution_cell->diameter(), solution_neighbor->diameter());
949 const double h_normal = std::min(face_normal_cell_width(solution_cell, face_no),
950 face_normal_cell_width(solution_neighbor, neighbor_face_no));
951 const uint degree = potential_fe.degree;
952 const double tau = 10. * (degree + 1.) * (degree + 1.) / h_face;
953 const double gradient_jump_weight = smoothing_length * smoothing_length / h_normal;
954
955 for (const auto q : potential_fe_interface_values.quadrature_point_indices()) {
956 const auto normal = potential_fe_interface_values.normal_vector(q);
957 const auto eom_s = evaluate_gradient(solution_cell, q_points[q], solution_values_s[q]);
958 const auto eom_n = evaluate_gradient(solution_neighbor, q_points[q], solution_values_n[q]);
959 const auto average_eom = 0.5 * (eom_s + eom_n);
960 const double rhs_flux = scalar_product(average_eom, normal);
961
962 for (uint i = 0; i < n_interface_dofs; ++i) {
963 const double jump_i = potential_view.jump_in_values(i, q);
964 const Tensor<1, dim> average_grad_i = potential_view.average_of_gradients(i, q);
965 const double normal_gradient_jump_i = scalar_product(potential_view.jump_in_gradients(i, q), normal);
966
967 face_data.rhs(i) += -potential_fe_interface_values.JxW(q) * rhs_flux * jump_i;
968
969 if (!assemble_matrix) continue;
970 for (uint j = 0; j < n_interface_dofs; ++j) {
971 const double jump_j = potential_view.jump_in_values(j, q);
972 const Tensor<1, dim> average_grad_j = potential_view.average_of_gradients(j, q);
973 const double normal_gradient_jump_j = scalar_product(potential_view.jump_in_gradients(j, q), normal);
974
975 face_data.matrix(i, j) +=
976 potential_fe_interface_values.JxW(q) *
977 (-scalar_product(average_grad_j, normal) * jump_i - scalar_product(average_grad_i, normal) * jump_j +
978 tau * jump_j * jump_i + gradient_jump_weight * normal_gradient_jump_i * normal_gradient_jump_j);
979 }
980 }
981 }
982 };
983
984 Scratch scratch(mapping, solution_dof_handler.get_fe(), potential_fe, quadrature, face_quadrature);
985 Copy copy;
986 const MeshWorker::AssembleFlags flags = MeshWorker::assemble_own_cells | MeshWorker::assemble_boundary_faces |
987 MeshWorker::assemble_own_interior_faces_once;
988
989 // Driven by the POTENTIAL DoFHandler, not the solution one. Under the distributed policy the
990 // solution handler sits on a partitioned triangulation, and MeshWorker::mesh_loop then skips
991 // every cell this rank does not own and every face between two cells it does not own -- and
992 // those face skips cannot be recovered with any combination of AssembleFlags. Since
993 // solve_potential gives the potential its own serial copy of the mesh, driving from that side
994 // makes the traversal complete, identical on every rank, and unchanged in the serial case
995 // (there the two handlers share a triangulation, so this is the same loop it always was).
996 MeshWorker::mesh_loop(potential_dof_handler.begin_active(), potential_dof_handler.end(), cell_worker, copier,
997 scratch, copy, flags, boundary_worker, face_worker);
998 }
999
1000 template <int dim, typename NumberType> struct PotentialMinimum {
1001 dealii::Point<dim> point;
1002 double value = std::numeric_limits<double>::max();
1003 };
1004
1005 template <int dim> struct LocalPotentialEvaluation {
1006 double value = 0.;
1007 dealii::Tensor<1, dim> gradient;
1008 dealii::Tensor<2, dim> hessian;
1009 };
1010
1011 template <int dim>
1012 LocalPotentialEvaluation<dim> evaluate_local_potential(const dealii::FiniteElement<dim> &potential_fe,
1013 const std::vector<double> &local_values,
1014 const dealii::Point<dim> &point)
1015 {
1017 for (uint i = 0; i < potential_fe.n_dofs_per_cell(); ++i) {
1018 evaluation.value += local_values[i] * potential_fe.shape_value(i, point);
1019 evaluation.gradient += local_values[i] * potential_fe.shape_grad(i, point);
1020 evaluation.hessian += local_values[i] * potential_fe.shape_grad_grad(i, point);
1021 }
1022 return evaluation;
1023 }
1024
1025 template <int dim>
1026 bool projected_newton_direction(const dealii::Tensor<2, dim> &hessian,
1027 const dealii::Tensor<1, dim> &projected_gradient,
1028 const std::array<bool, dim> &fixed, dealii::Tensor<1, dim> &direction)
1029 {
1030 std::array<uint, dim> free_indices{};
1031 uint n_free = 0;
1032 for (uint d = 0; d < dim; ++d)
1033 if (!fixed[d]) free_indices[n_free++] = d;
1034
1035 if (n_free == 0) return false;
1036
1037 Eigen::MatrixXd reduced_hessian(n_free, n_free);
1038 Eigen::VectorXd reduced_gradient(n_free);
1039 double hessian_norm = 0.;
1040 for (uint i = 0; i < n_free; ++i) {
1041 reduced_gradient[i] = projected_gradient[free_indices[i]];
1042 for (uint j = 0; j < n_free; ++j) {
1043 reduced_hessian(i, j) = hessian[free_indices[i]][free_indices[j]];
1044 hessian_norm = std::max(hessian_norm, std::abs(reduced_hessian(i, j)));
1045 }
1046 }
1047 if (!reduced_hessian.allFinite() || !reduced_gradient.allFinite()) return false;
1048
1049 const double positivity_threshold = 100. * std::numeric_limits<double>::epsilon() * std::max(1., hessian_norm);
1050 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> solver(reduced_hessian);
1051 if (solver.info() != Eigen::Success || !solver.eigenvalues().allFinite() ||
1052 !(solver.eigenvalues().minCoeff() > positivity_threshold))
1053 return false;
1054
1055 const Eigen::VectorXd reduced_direction =
1056 -solver.eigenvectors() *
1057 (solver.eigenvectors().transpose() * reduced_gradient).cwiseQuotient(solver.eigenvalues());
1058 if (!reduced_direction.allFinite()) return false;
1059
1060 direction = {};
1061 for (uint i = 0; i < n_free; ++i)
1062 direction[free_indices[i]] = reduced_direction[i];
1063 return true;
1064 }
1065
1066 template <int dim, typename NumberType>
1067 PotentialMinimum<dim, NumberType>
1068 refine_cell_minimum(const typename dealii::DoFHandler<dim>::active_cell_iterator &cell,
1069 const dealii::FiniteElement<dim> &potential_fe, const dealii::Mapping<dim> &mapping,
1070 const dealii::Vector<NumberType> &potential, const dealii::Quadrature<dim> &quadrature,
1072 const std::optional<dealii::Point<dim>> &initial_unit_point = std::nullopt)
1073 {
1074 std::vector<dealii::types::global_dof_index> local_dof_indices(potential_fe.n_dofs_per_cell());
1075 cell->get_dof_indices(local_dof_indices);
1076
1077 std::vector<double> local_values(potential_fe.n_dofs_per_cell());
1078 for (uint i = 0; i < potential_fe.n_dofs_per_cell(); ++i)
1079 local_values[i] = (double)potential[local_dof_indices[i]];
1080
1081 dealii::Point<dim> sampled_point = quadrature.point(0);
1082 auto sampled_evaluation = evaluate_local_potential(potential_fe, local_values, sampled_point);
1083 const auto consider_seed = [&](const dealii::Point<dim> &candidate) {
1084 const auto candidate_evaluation = evaluate_local_potential(potential_fe, local_values, candidate);
1085 if (candidate_evaluation.value < sampled_evaluation.value) {
1086 sampled_point = candidate;
1087 sampled_evaluation = candidate_evaluation;
1088 }
1089 };
1090
1091 for (const auto &support_point : potential_fe.get_unit_support_points())
1092 consider_seed(support_point);
1093 for (const auto &quadrature_point : quadrature.get_points())
1094 consider_seed(quadrature_point);
1095
1096 const double gradient_tolerance = std::max(config.abs_tol, 100. * std::numeric_limits<double>::epsilon());
1097
1098 const auto refine_from_seed = [&](dealii::Point<dim> point) {
1099 auto evaluation = evaluate_local_potential(potential_fe, local_values, point);
1100 for (uint iteration = 0; iteration < config.max_iter; ++iteration) {
1101 std::array<bool, dim> fixed{};
1102 dealii::Tensor<1, dim> projected_gradient;
1103 double projected_gradient_norm = 0.;
1104 for (uint d = 0; d < dim; ++d) {
1105 const bool fixed_at_lower = point[d] <= config.bound_tolerance && evaluation.gradient[d] > 0.;
1106 const bool fixed_at_upper = point[d] >= 1. - config.bound_tolerance && evaluation.gradient[d] < 0.;
1107 fixed[d] = fixed_at_lower || fixed_at_upper;
1108 projected_gradient[d] = fixed[d] ? 0. : evaluation.gradient[d];
1109 const double extent = std::max(cell->extent_in_direction(d), std::numeric_limits<double>::epsilon());
1110 projected_gradient_norm = std::max(projected_gradient_norm, std::abs(projected_gradient[d]) / extent);
1111 }
1112 if (projected_gradient_norm <= gradient_tolerance) break;
1113
1114 dealii::Tensor<1, dim> direction;
1115 bool used_newton = projected_newton_direction<dim>(evaluation.hessian, projected_gradient, fixed, direction);
1116 double directional_derivative = dealii::scalar_product(evaluation.gradient, direction);
1117 if (!used_newton || !(directional_derivative < 0.) || !std::isfinite(directional_derivative)) {
1118 direction = -projected_gradient;
1119 directional_derivative = dealii::scalar_product(evaluation.gradient, direction);
1120 used_newton = false;
1121 }
1122 if (!(directional_derivative < 0.) || !std::isfinite(directional_derivative)) break;
1123
1124 const auto try_step = [&](const dealii::Tensor<1, dim> &search_direction, const double search_derivative,
1125 dealii::Point<dim> &accepted_point,
1126 LocalPotentialEvaluation<dim> &accepted_evaluation) {
1127 double alpha = 1.;
1128 for (uint d = 0; d < dim; ++d) {
1129 if (search_direction[d] > 0.)
1130 alpha = std::min(alpha, (1. - point[d]) / search_direction[d]);
1131 else if (search_direction[d] < 0.)
1132 alpha = std::min(alpha, -point[d] / search_direction[d]);
1133 }
1134 if (!(alpha > 0.) || !std::isfinite(alpha)) return false;
1135
1136 for (uint backtrack = 0; backtrack < config.max_backtracks; ++backtrack) {
1137 auto candidate = point;
1138 for (uint d = 0; d < dim; ++d)
1139 candidate[d] = std::clamp(point[d] + alpha * search_direction[d], 0., 1.);
1140 const auto candidate_evaluation = evaluate_local_potential(potential_fe, local_values, candidate);
1141 if (candidate_evaluation.value <=
1142 evaluation.value + config.armijo_coefficient * alpha * search_derivative) {
1143 accepted_point = candidate;
1144 accepted_evaluation = candidate_evaluation;
1145 return true;
1146 }
1147 alpha *= 0.5;
1148 }
1149 return false;
1150 };
1151
1152 dealii::Point<dim> next_point;
1153 LocalPotentialEvaluation<dim> next_evaluation;
1154 bool accepted = try_step(direction, directional_derivative, next_point, next_evaluation);
1155 if (!accepted && used_newton) {
1156 direction = -projected_gradient;
1157 directional_derivative = dealii::scalar_product(evaluation.gradient, direction);
1158 if (directional_derivative < 0.)
1159 accepted = try_step(direction, directional_derivative, next_point, next_evaluation);
1160 }
1161 if (!accepted || !(next_evaluation.value < evaluation.value)) break;
1162
1163 point = next_point;
1164 evaluation = next_evaluation;
1165 }
1166 return std::pair{point, evaluation};
1167 };
1168
1169 auto [point, evaluation] = refine_from_seed(sampled_point);
1170 if (initial_unit_point) {
1171 auto warm_start = *initial_unit_point;
1172 bool valid = true;
1173 for (uint d = 0; d < dim; ++d) {
1174 valid = valid && std::isfinite(warm_start[d]) && warm_start[d] >= -config.bound_tolerance &&
1175 warm_start[d] <= 1. + config.bound_tolerance;
1176 warm_start[d] = std::clamp(warm_start[d], 0., 1.);
1177 }
1178 if (valid) {
1179 auto [warm_point, warm_evaluation] = refine_from_seed(warm_start);
1180 if (warm_evaluation.value < evaluation.value) {
1181 point = warm_point;
1182 evaluation = warm_evaluation;
1183 }
1184 }
1185 }
1186
1187 return {.point = mapping.transform_unit_to_real_cell(cell, point), .value = evaluation.value};
1188 }
1189
1190 template <int dim, typename NumberType>
1191 PotentialMinimum<dim, NumberType>
1192 find_potential_minimum(const dealii::DoFHandler<dim> &potential_dof_handler,
1193 const dealii::FiniteElement<dim> &potential_fe, const dealii::Mapping<dim> &mapping,
1194 const dealii::Vector<NumberType> &potential, const dealii::Quadrature<dim> &quadrature,
1196 const std::optional<dealii::Point<dim>> &initial_guess = std::nullopt,
1197 const std::map<dealii::types::global_dof_index, dealii::Point<dim>> *cached_support_points =
1198 nullptr)
1199 {
1201
1202 const auto own_support_points = cached_support_points != nullptr
1203 ? std::map<dealii::types::global_dof_index, dealii::Point<dim>>{}
1204 : DoFTools::map_dofs_to_support_points(mapping, potential_dof_handler);
1205 const auto &support_points = cached_support_points != nullptr ? *cached_support_points : own_support_points;
1206 for (const auto &dof_and_point : support_points) {
1207 const auto dof = dof_and_point.first;
1208 if ((double)potential[dof] < minimum.value) {
1209 minimum.value = (double)potential[dof];
1210 minimum.point = dof_and_point.second;
1211 }
1212 }
1213
1214 FEValues<dim> potential_fe_values(mapping, potential_fe, quadrature, update_values | update_quadrature_points);
1215 std::vector<NumberType> potential_values(quadrature.size());
1216 for (const auto &cell : potential_dof_handler.active_cell_iterators()) {
1217 potential_fe_values.reinit(cell);
1218 potential_fe_values.get_function_values(potential, potential_values);
1219
1220 for (const auto q : potential_fe_values.quadrature_point_indices()) {
1221 if ((double)potential_values[q] < minimum.value) {
1222 minimum.value = (double)potential_values[q];
1223 minimum.point = potential_fe_values.quadrature_point(q);
1224 }
1225 }
1226 }
1227
1228 const auto minimum_cell =
1229 GridTools::find_active_cell_around_point(mapping, potential_dof_handler, minimum.point).first;
1230 using CellIterator = typename dealii::DoFHandler<dim>::active_cell_iterator;
1231 struct CandidateCell {
1232 CellIterator cell;
1233 std::optional<dealii::Point<dim>> initial_unit_point;
1234 };
1235 std::vector<CandidateCell> candidate_cells;
1236 const auto add_candidate = [&](const CellIterator &cell,
1237 const std::optional<dealii::Point<dim>> &initial_unit_point = std::nullopt) {
1238 const auto candidate = std::find_if(candidate_cells.begin(), candidate_cells.end(),
1239 [&](const auto &entry) { return entry.cell == cell; });
1240 if (candidate == candidate_cells.end())
1241 candidate_cells.push_back({cell, initial_unit_point});
1242 else if (initial_unit_point)
1243 candidate->initial_unit_point = initial_unit_point;
1244 };
1245 const auto add_cell_and_neighbors = [&](const CellIterator &cell) {
1246 add_candidate(cell);
1247 std::vector<CellIterator> active_neighbors;
1248 GridTools::get_active_neighbors<dealii::DoFHandler<dim>>(cell, active_neighbors);
1249 for (const auto &neighbor : active_neighbors)
1250 add_candidate(neighbor);
1251 };
1252
1253 add_cell_and_neighbors(minimum_cell);
1254 if (initial_guess) {
1255 bool finite = true;
1256 for (uint d = 0; d < dim; ++d)
1257 finite = finite && std::isfinite((*initial_guess)[d]);
1258 const auto bounding_box = GridTools::compute_bounding_box(potential_dof_handler.get_triangulation());
1259 if (finite && bounding_box.point_inside(*initial_guess)) {
1260 try {
1261 const auto initial_cell =
1262 GridTools::find_active_cell_around_point(mapping, potential_dof_handler, *initial_guess).first;
1263 if (initial_cell != potential_dof_handler.end()) {
1264 add_cell_and_neighbors(initial_cell);
1265 add_candidate(initial_cell, mapping.transform_real_to_unit_cell(initial_cell, *initial_guess));
1266 }
1267 } catch (const dealii::ExceptionBase &) {
1268 }
1269 }
1270 }
1271
1272 for (const auto &candidate : candidate_cells) {
1273 const auto &cell = candidate.cell;
1274 const auto refined = refine_cell_minimum<dim, NumberType>(cell, potential_fe, mapping, potential, quadrature,
1275 config, candidate.initial_unit_point);
1276 if (refined.value < minimum.value) minimum = refined;
1277 }
1278
1279 return minimum;
1280 }
1281
1282 template <int dim, typename VectorType, typename GradientFUN>
1284 solve_potential(const VectorType &sol, const dealii::DoFHandler<dim> &solution_dof_handler,
1285 const dealii::Mapping<dim> &mapping, const GradientFUN &get_gradient,
1286 const Config::EoMConfig &config, const PotentialKind kind,
1288 {
1289 using NumberType = typename VectorType::value_type;
1290
1291 auto &solve_stats = potential_solve_stats();
1292 ++solve_stats.solves;
1293 ScopedTimer solve_timer(solve_stats.seconds);
1294
1295 auto origin_cell = solution_dof_handler.begin_active();
1296 const auto origin = get_origin(solution_dof_handler, origin_cell);
1297 const double smoothing_length = resolve_potential_smoothing_length(solution_dof_handler, config.smoothing_length);
1298
1299 // The potential gets its own SERIAL mesh whenever the solution lives on a partitioned one.
1300 //
1301 // Two things force this, and either alone is fatal:
1302 // * distribute_dofs() on a parallel triangulation is collective (MPI_Allgather inside
1303 // ParallelShared::distribute_dofs), while this whole call chain runs inside readouts(),
1304 // which OutputSession invokes on rank 0 only. Rank 0 would enter a collective nobody
1305 // else reaches, and the run hangs with no diagnostic.
1306 // * even if it did not hang, MeshWorker::mesh_loop over a partitioned mesh visits only the
1307 // calling rank's cells, so the potential system would be assembled with entire rows left
1308 // at zero -- a singular matrix handed to UMFPACK, or silently wrong readouts.
1309 //
1310 // The mesh is replicated at this rung and the solution is a full replica, so a serial copy is
1311 // complete and every rank would compute the identical potential from it.
1312 const auto &potential_triangulation = serial_mirror(solution_dof_handler.get_triangulation());
1313
1314 // Callers that pass no cache get one that lives for this call only, which is the behaviour this
1315 // function had throughout: build everything, factorize, solve, throw it all away.
1317 auto &system = (cache != nullptr ? *cache : uncached).entry(kind);
1318
1319 // The eom potential is always quadratic; the raw potential's order is a config choice, so a
1320 // change of /discretization/EoM/raw_potential_order must invalidate the cached system.
1321 const uint potential_order = kind == PotentialKind::eom ? 2 : config.raw_potential_order;
1322 const bool rebuild =
1323 !system.describes(potential_triangulation, solution_dof_handler, smoothing_length, potential_order);
1324 if (rebuild) {
1325 system.finite_element = make_potential_fe<dim>(kind, potential_order);
1326 system.dof_handler = std::make_shared<DoFHandler<dim>>(potential_triangulation);
1327 system.dof_handler->distribute_dofs(*system.finite_element);
1328
1329 system.support_points = DoFTools::map_dofs_to_support_points(mapping, *system.dof_handler);
1330
1331 system.constraints.clear();
1332 DoFTools::make_hanging_node_constraints(*system.dof_handler, system.constraints);
1333 const auto gauge_dof = select_gauge_dof(system.constraints, origin, system.support_points);
1334 system.constraints.add_line(gauge_dof);
1335 system.constraints.set_inhomogeneity(gauge_dof, 0.);
1336 system.constraints.close();
1337
1338 DynamicSparsityPattern dsp(system.dof_handler->n_dofs());
1339 DoFTools::make_flux_sparsity_pattern(*system.dof_handler, dsp, system.constraints,
1340 /*keep_constrained_dofs = */ true);
1341 system.matrix.clear();
1342 system.sparsity_pattern.copy_from(dsp);
1343 system.matrix.reinit(system.sparsity_pattern);
1344
1345 const uint quadrature_order = std::max<uint>(
1346 std::max<uint>(solution_dof_handler.get_fe().degree, system.finite_element->degree) + 2, 2);
1347 system.quadrature = QGauss<dim>(quadrature_order);
1348 system.face_quadrature = QGauss<dim - 1>(quadrature_order);
1349 }
1350
1351 Vector<NumberType> rhs(system.dof_handler->n_dofs());
1352 assemble_potential_system(sol, solution_dof_handler, *system.dof_handler, *system.finite_element, mapping,
1353 get_gradient, system.quadrature, system.face_quadrature, system.constraints,
1354 system.matrix, rhs, smoothing_length, /*assemble_matrix = */ rebuild);
1355
1356 if (rebuild) {
1357 system.factorization.initialize(system.matrix);
1358 system.note_built_for(potential_triangulation, solution_dof_handler, smoothing_length);
1359 }
1360
1361 Vector<NumberType> potential(system.dof_handler->n_dofs());
1362 system.factorization.vmult(potential, rhs);
1363 system.constraints.distribute(potential);
1364
1365 return {.finite_element = system.finite_element,
1366 .dof_handler = system.dof_handler,
1367 .values = std::move(potential)};
1368 }
1369
1370 template <int dim, typename VectorType, typename EoMFUN>
1372 reconstruct_potential(typename dealii::DoFHandler<dim>::cell_iterator &EoM_cell, const VectorType &sol,
1373 const dealii::DoFHandler<dim> &solution_dof_handler, const dealii::Mapping<dim> &mapping,
1374 const EoMFUN &get_EoM, const Config::EoMConfig &config,
1375 const std::optional<dealii::Point<dim>> &initial_guess = std::nullopt,
1377 {
1378 auto potential =
1379 solve_potential(sol, solution_dof_handler, mapping, get_EoM, config, PotentialKind::eom, cache);
1380
1381 // The minimum search wants the same quadrature the system was assembled with, and the same support
1382 // points the gauge choice used. Both come from the cache when there is one: deal.II builds Gauss
1383 // points by Newton iteration in long double, and the support point map is one node per DoF.
1384 const uint quadrature_order =
1385 std::max<uint>(std::max<uint>(solution_dof_handler.get_fe().degree, potential.finite_element->degree) + 2, 2);
1386 const std::optional<QGauss<dim>> own_quadrature =
1387 cache != nullptr ? std::nullopt : std::optional<QGauss<dim>>(quadrature_order);
1388 const auto *system = cache != nullptr ? &cache->entry(PotentialKind::eom) : nullptr;
1389
1390 const auto minimum = find_potential_minimum(
1391 *potential.dof_handler, *potential.finite_element, mapping, potential.values,
1392 system != nullptr ? system->quadrature : *own_quadrature, config, initial_guess,
1393 system != nullptr ? &system->support_points : nullptr);
1394 EoM_cell = GridTools::find_active_cell_around_point(solution_dof_handler, minimum.point);
1395 return {.minimum = minimum.point,
1396 .finite_element = std::move(potential.finite_element),
1397 .dof_handler = std::move(potential.dof_handler),
1398 .values = std::move(potential.values)};
1399 }
1400
1401 } // namespace internal
1402
1409 template <int dim, typename VectorType, typename GradientFUN>
1410 ReconstructedRawPotential<dim, typename VectorType::value_type>
1411 reconstruct_raw_potential(const VectorType &sol, const dealii::DoFHandler<dim> &dof_handler,
1412 const dealii::Mapping<dim> &mapping, const GradientFUN &get_gradient,
1415 {
1416 static_assert(dim > 0, "A raw spatial potential cannot be reconstructed in zero dimensions.");
1417 config.validate();
1418 auto potential =
1419 internal::solve_potential(sol, dof_handler, mapping, get_gradient, config, internal::PotentialKind::raw, cache);
1420 if (config.raw_potential_recover_mass_hessian && dof_handler.get_fe().degree == 0) {
1421 const auto models = internal::recover_dg0_gradient_models(sol, dof_handler, mapping, get_gradient);
1422 internal::recover_continuous_hessian_field(potential, models, dof_handler);
1423 if (config.raw_potential_mass_hessian_jump_threshold >= 0.)
1424 internal::prepare_eom_side_hessian_recovery(potential, models, dof_handler,
1425 config.raw_potential_mass_hessian_jump_threshold);
1426 }
1427 return potential;
1428 }
1429
1436 template <int dim, typename NumberType>
1437 RawPotentialEvaluation<dim, NumberType>
1439 const dealii::Mapping<dim> &mapping, const dealii::Point<dim> &point)
1440 {
1441 const auto cell = dealii::GridTools::find_active_cell_around_point(mapping, *potential.dof_handler, point).first;
1442 const auto unit_point = mapping.transform_real_to_unit_cell(cell, point);
1443 dealii::FEValues<dim> fe_values(mapping, *potential.finite_element, unit_point,
1444 dealii::update_values | dealii::update_gradients | dealii::update_hessians);
1445 fe_values.reinit(cell);
1446
1447 std::vector<NumberType> values(1);
1448 std::vector<dealii::Tensor<1, dim, NumberType>> gradients(1);
1449 std::vector<dealii::Tensor<2, dim, NumberType>> potential_hessians(1);
1450 fe_values.get_function_values(potential.values, values);
1451 fe_values.get_function_gradients(potential.values, gradients);
1452 fe_values.get_function_hessians(potential.values, potential_hessians);
1453 auto mass_hessian = potential_hessians[0];
1454 if (potential.recovered_mass_hessian) {
1455 const auto &recovery = *potential.recovered_mass_hessian;
1456 const auto hessian_cell = internal::matching_dof_cell(*recovery.dof_handler, cell);
1457 dealii::FEValues<dim> hessian_fe_values(mapping, *recovery.finite_element, unit_point, dealii::update_values);
1458 hessian_fe_values.reinit(hessian_cell);
1459 for (uint d = 0; d < dim; ++d)
1460 for (uint e = 0; e < dim; ++e) {
1461 std::vector<NumberType> component_values(1);
1462 hessian_fe_values.get_function_values(recovery.values[d * dim + e], component_values);
1463 mass_hessian[d][e] = component_values[0];
1464 }
1465 mass_hessian = 0.5 * (mass_hessian + transpose(mass_hessian));
1466 if (const auto eom_side_hessian =
1467 internal::evaluate_eom_side_hessian(potential, cell->active_cell_index(), point)) {
1468 const auto blend = internal::eom_side_hessian_blend(potential, cell->active_cell_index(), point);
1469 const auto correction = *eom_side_hessian - mass_hessian;
1470 const double normal_correction = blend.normal * correction * blend.normal;
1471 mass_hessian += blend.weight * normal_correction * outer_product(blend.normal, blend.normal);
1472 }
1473 }
1474 return {.value = values[0],
1475 .gradient = gradients[0],
1476 .potential_hessian = potential_hessians[0],
1477 .mass_hessian = mass_hessian};
1478 }
1479
1484 template <int dim, typename VectorType, typename EoMFUN, typename EoMPFUN>
1485 EoMResult<dim, typename VectorType::value_type>
1486 get_EoM_point_with_potential(typename dealii::DoFHandler<dim>::cell_iterator &EoM_cell, const VectorType &sol,
1487 const dealii::DoFHandler<dim> &dof_handler, const dealii::Mapping<dim> &mapping,
1488 const EoMFUN &get_EoM, const EoMPFUN &EoM_postprocess, const Config::EoMConfig &config,
1489 const std::optional<dealii::Point<dim>> &initial_guess = std::nullopt,
1491 {
1492 config.validate();
1493 if (config.max_iter == 0) return {.point = internal::get_origin(dof_handler, EoM_cell), .potential = std::nullopt};
1494
1495 auto potential =
1496 internal::reconstruct_potential(EoM_cell, sol, dof_handler, mapping, get_EoM, config, initial_guess, cache);
1497 auto EoM = potential.minimum;
1498
1499 dealii::Vector<typename VectorType::value_type> values(dof_handler.get_fe().n_components());
1500 dealii::Functions::FEFieldFunction<dim, VectorType> fe_function(dof_handler, sol, mapping);
1501 fe_function.set_active_cell(EoM_cell);
1502 fe_function.vector_value(EoM, values);
1503
1504 EoM = EoM_postprocess(EoM, values);
1505 EoM_cell = dealii::GridTools::find_active_cell_around_point(dof_handler, EoM);
1506
1507 return {.point = EoM, .potential = std::move(potential)};
1508 }
1509
1511 template <int dim, typename VectorType, typename EoMFUN, typename EoMPFUN>
1513 typename dealii::DoFHandler<dim>::cell_iterator &EoM_cell, const VectorType &sol,
1514 const dealii::DoFHandler<dim> &dof_handler, const dealii::Mapping<dim> &mapping, const EoMFUN &get_EoM,
1515 const EoMPFUN &EoM_postprocess = [](const auto &p, [[maybe_unused]] const auto &values) { return p; },
1516 const double EoM_abs_tol = Config::EoMConfig::default_abs_tol,
1518 const double EoM_smoothing_length = Config::EoMConfig::default_smoothing_length,
1519 const std::optional<dealii::Point<dim>> &initial_guess = std::nullopt,
1520 internal::PotentialSystemCache<dim, typename VectorType::value_type> *cache = nullptr)
1521 {
1522 return get_EoM_point_with_potential(EoM_cell, sol, dof_handler, mapping, get_EoM, EoM_postprocess,
1523 Config::EoMConfig(EoM_abs_tol, max_iter, EoM_smoothing_length), initial_guess,
1524 cache);
1525 }
1526
1531 template <int dim, typename VectorType, typename EoMFUN, typename EoMPFUN>
1532 dealii::Point<dim> get_EoM_point(typename dealii::DoFHandler<dim>::cell_iterator &EoM_cell, const VectorType &sol,
1533 const dealii::DoFHandler<dim> &dof_handler, const dealii::Mapping<dim> &mapping,
1534 const EoMFUN &get_EoM, const EoMPFUN &EoM_postprocess,
1536 const std::optional<dealii::Point<dim>> &initial_guess = std::nullopt,
1538 nullptr)
1539 {
1540 return get_EoM_point_with_potential(EoM_cell, sol, dof_handler, mapping, get_EoM, EoM_postprocess, config,
1541 initial_guess, cache)
1542 .point;
1543 }
1544
1546 template <int dim, typename VectorType, typename EoMFUN, typename EoMPFUN>
1547 dealii::Point<dim> get_EoM_point(
1548 typename dealii::DoFHandler<dim>::cell_iterator &EoM_cell, const VectorType &sol,
1549 const dealii::DoFHandler<dim> &dof_handler, const dealii::Mapping<dim> &mapping, const EoMFUN &get_EoM,
1550 const EoMPFUN &EoM_postprocess = [](const auto &p, [[maybe_unused]] const auto &values) { return p; },
1551 const double EoM_abs_tol = Config::EoMConfig::default_abs_tol,
1553 const double EoM_smoothing_length = Config::EoMConfig::default_smoothing_length,
1554 const std::optional<dealii::Point<dim>> &initial_guess = std::nullopt)
1555 {
1556 return get_EoM_point(EoM_cell, sol, dof_handler, mapping, get_EoM, EoM_postprocess,
1557 Config::EoMConfig(EoM_abs_tol, max_iter, EoM_smoothing_length), initial_guess);
1558 }
1559} // namespace DiFfRG
Adds the wall time of its scope to a double, in seconds.
Definition output_timings.hh:97
The mesh-dependent half of solve_potential, retained across calls.
Definition eom.hh:315
std::array< std::unique_ptr< Entry >, n_potential_kinds > entries
Definition eom.hh:389
static constexpr int face_dim
Definition eom.hh:316
Entry & entry(const PotentialKind kind)
The entry for one kind of potential, created empty on first use.
Definition eom.hh:381
constexpr double minimum_rank_one_confidence
Definition eom.hh:507
EoMSideHessianBlend< dim > eom_side_hessian_blend(const ReconstructedRawPotential< dim, NumberType > &potential, const uint root_cell, const dealii::Point< dim > &point)
Definition eom.hh:629
dealii::Tensor< 1, dim, NumberType > evaluate_dg0_gradient_model(const DG0GradientModel< dim, NumberType > &model, const dealii::Point< dim > &point)
Definition eom.hh:480
void prepare_eom_side_hessian_recovery(ReconstructedRawPotential< dim, NumberType > &potential, const std::vector< DG0GradientModel< dim, NumberType > > &models, const dealii::DoFHandler< dim > &solution_dof_handler, const double jump_threshold)
Definition eom.hh:555
std::vector< DG0GradientModel< dim, typename VectorType::value_type > > recover_dg0_gradient_models(const VectorType &sol, const dealii::DoFHandler< dim > &solution_dof_handler, const dealii::Mapping< dim > &mapping, const GradientFUN &get_gradient)
Definition eom.hh:408
std::unique_ptr< dealii::FiniteElement< dim > > make_eom_potential_fe()
Definition eom.hh:279
std::unique_ptr< dealii::FiniteElement< dim > > make_potential_fe(const PotentialKind kind, const uint raw_order)
Definition eom.hh:294
PotentialSolveStats & potential_solve_stats()
Definition eom.hh:70
constexpr uint n_potential_kinds
Definition eom.hh:291
void recover_continuous_hessian_field(ReconstructedRawPotential< dim, NumberType > &potential, const std::vector< DG0GradientModel< dim, NumberType > > &models, const dealii::DoFHandler< dim > &solution_dof_handler)
Definition eom.hh:658
double minimum_face_normal_cell_width(const dealii::DoFHandler< dim > &dof_handler)
The smallest face-normal width anywhere on the mesh.
Definition cell_geometry.hh:51
PotentialMinimum< dim, NumberType > refine_cell_minimum(const typename dealii::DoFHandler< dim >::active_cell_iterator &cell, const dealii::FiniteElement< dim > &potential_fe, const dealii::Mapping< dim > &mapping, const dealii::Vector< NumberType > &potential, const dealii::Quadrature< dim > &quadrature, const Config::EoMConfig &config, const std::optional< dealii::Point< dim > > &initial_unit_point=std::nullopt)
Definition eom.hh:1068
dealii::types::global_dof_index select_gauge_dof(const dealii::AffineConstraints< NumberType > &constraints, const dealii::Point< dim > &origin, const std::map< dealii::types::global_dof_index, dealii::Point< dim > > &support_points)
Definition eom.hh:257
ReconstructedEoMPotential< dim, typename VectorType::value_type > reconstruct_potential(typename dealii::DoFHandler< dim >::cell_iterator &EoM_cell, const VectorType &sol, const dealii::DoFHandler< dim > &solution_dof_handler, const dealii::Mapping< dim > &mapping, const EoMFUN &get_EoM, const Config::EoMConfig &config, const std::optional< dealii::Point< dim > > &initial_guess=std::nullopt, PotentialSystemCache< dim, typename VectorType::value_type > *cache=nullptr)
Definition eom.hh:1372
LocalPotentialEvaluation< dim > evaluate_local_potential(const dealii::FiniteElement< dim > &potential_fe, const std::vector< double > &local_values, const dealii::Point< dim > &point)
Definition eom.hh:1012
dealii::Point< dim > get_origin(const dealii::DoFHandler< dim > &dof_handler, typename dealii::DoFHandler< dim >::cell_iterator &EoM_cell)
Definition eom.hh:173
std::optional< dealii::Tensor< 2, dim, NumberType > > evaluate_eom_side_hessian(const ReconstructedRawPotential< dim, NumberType > &potential, const uint root_cell, const dealii::Point< dim > &point)
Definition eom.hh:710
Config::EoMConfig resolve_eom_config(const dealii::DoFHandler< dim > &dof_handler, Config::EoMConfig config)
Definition eom.hh:248
ReconstructedRawPotential< dim, typename VectorType::value_type > solve_potential(const VectorType &sol, const dealii::DoFHandler< dim > &solution_dof_handler, const dealii::Mapping< dim > &mapping, const GradientFUN &get_gradient, const Config::EoMConfig &config, const PotentialKind kind, PotentialSystemCache< dim, typename VectorType::value_type > *cache)
Definition eom.hh:1284
PotentialMinimum< dim, NumberType > find_potential_minimum(const dealii::DoFHandler< dim > &potential_dof_handler, const dealii::FiniteElement< dim > &potential_fe, const dealii::Mapping< dim > &mapping, const dealii::Vector< NumberType > &potential, const dealii::Quadrature< dim > &quadrature, const Config::EoMConfig &config, const std::optional< dealii::Point< dim > > &initial_guess=std::nullopt, const std::map< dealii::types::global_dof_index, dealii::Point< dim > > *cached_support_points=nullptr)
Definition eom.hh:1192
dealii::Tensor< 1, dim > eom_to_tensor(const EoMValue &eom)
Definition eom.hh:392
PotentialKind
Which of the two potentials a reconstruction is for; they are cached separately.
Definition eom.hh:290
std::unique_ptr< dealii::FiniteElement< dim > > make_raw_potential_fe(const uint order=2)
Definition eom.hh:284
void assemble_potential_system(const VectorType &sol, const dealii::DoFHandler< dim > &solution_dof_handler, const dealii::DoFHandler< dim > &potential_dof_handler, const dealii::FiniteElement< dim > &potential_fe, const dealii::Mapping< dim > &mapping, const EoMFUN &get_EoM, const dealii::Quadrature< dim > &quadrature, const dealii::Quadrature< dim - 1 > &face_quadrature, const dealii::AffineConstraints< typename VectorType::value_type > &constraints, dealii::SparseMatrix< typename VectorType::value_type > &matrix, dealii::Vector< typename VectorType::value_type > &rhs, const double smoothing_length, const bool assemble_matrix)
Definition eom.hh:854
double face_normal_cell_width(const CellIterator &cell, const uint face_no)
The extent of a cell along the normal of one of its faces.
Definition cell_geometry.hh:23
dealii::Tensor< 2, dim, NumberType > symmetric_jacobian(const DG0GradientModel< dim, NumberType > &model)
Definition eom.hh:491
dealii::DoFHandler< dim >::active_cell_iterator matching_dof_cell(const dealii::DoFHandler< dim > &target_dof_handler, const typename dealii::DoFHandler< dim >::active_cell_iterator &source_cell)
The cell at the same position in another DoFHandler.
Definition eom.hh:219
double resolve_potential_smoothing_length(const dealii::DoFHandler< dim > &dof_handler, const double configured_length)
Definition eom.hh:235
HessianJumpCompatibility< dim > hessian_jump_compatibility(const dealii::Tensor< 2, dim, NumberType > &left, const dealii::Tensor< 2, dim, NumberType > &right, const dealii::Tensor< 1, dim > &coordinate_scale)
Definition eom.hh:511
double l1_distance(const dealii::Point< dim > &a, const dealii::Point< dim > &b)
Definition eom.hh:226
bool projected_newton_direction(const dealii::Tensor< 2, dim > &hessian, const dealii::Tensor< 1, dim > &projected_gradient, const std::array< bool, dim > &fixed, dealii::Tensor< 1, dim > &direction)
Definition eom.hh:1026
Definition complex_math.hh:10
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
@ config
/discretization/threads.
UnusedPotentialEvaluation evaluate_raw_potential(const UnusedPotential &, const dealii::Mapping< dim > &, const dealii::Point< dim > &)
Definition eom.hh:149
unsigned int uint
Definition utils.hh:24
const dealii::Triangulation< dim > & serial_mirror(const dealii::Triangulation< dim > &source)
A process-local, serial mirror of a (possibly partitioned) triangulation.
Definition serial_mirror.hh:47
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
dealii::Point< dim > get_EoM_point(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:1532
Definition eom_config.hh:10
static constexpr unsigned int default_max_iter
Definition eom_config.hh:12
static constexpr double default_smoothing_length
Definition eom_config.hh:13
static constexpr double default_abs_tol
Definition eom_config.hh:11
Result of finding an EoM point, optionally including its reconstructed potential.
Definition eom.hh:163
std::optional< ReconstructedEoMPotential< dim, NumberType > > potential
Definition eom.hh:165
dealii::Point< dim > point
Definition eom.hh:164
Reconstructed potential data and the explicitly separate Hessian used for mass extraction.
Definition eom.hh:127
dealii::Tensor< 2, dim, NumberType > potential_hessian
Definition eom.hh:130
NumberType value
Definition eom.hh:128
dealii::Tensor< 1, dim, NumberType > gradient
Definition eom.hh:129
dealii::Tensor< 2, dim, NumberType > mass_hessian
Definition eom.hh:131
dealii::Point< 0 > minimum
Definition eom.hh:156
dealii::Vector< NumberType > values
Definition eom.hh:157
An owning scalar potential reconstructed from a model EoM vector field.
Definition eom.hh:83
dealii::Point< dim > minimum
Definition eom.hh:84
dealii::Vector< NumberType > values
Definition eom.hh:87
std::shared_ptr< dealii::DoFHandler< dim > > dof_handler
Definition eom.hh:86
std::shared_ptr< dealii::FiniteElement< dim > > finite_element
Definition eom.hh:85
dealii::Vector< NumberType > values
Definition eom.hh:123
std::unique_ptr< RecoveredMassHessian< dim, NumberType > > recovered_mass_hessian
Definition eom.hh:119
std::shared_ptr< dealii::DoFHandler< dim > > dof_handler
Definition eom.hh:117
std::shared_ptr< dealii::FiniteElement< dim > > finite_element
Definition eom.hh:116
dealii::Vector< NumberType > values
Definition eom.hh:118
A scalar potential reconstructed from a model-provided raw gradient.
Definition eom.hh:100
std::vector< std::vector< double > > face_weights
Definition eom.hh:107
std::unique_ptr< dealii::FiniteElement< dim > > finite_element
Definition eom.hh:101
std::vector< std::vector< uint > > kink_faces_by_cell
Definition eom.hh:108
std::vector< dealii::Tensor< 1, dim > > kink_face_normals
Definition eom.hh:110
std::vector< double > kink_face_widths
Definition eom.hh:111
std::vector< std::vector< uint > > neighbors
Definition eom.hh:106
std::vector< dealii::Point< dim > > kink_face_centers
Definition eom.hh:109
std::array< dealii::Vector< NumberType >, dim *dim > values
Definition eom.hh:103
std::vector< dealii::Point< dim > > sample_centers
Definition eom.hh:104
std::vector< dealii::Tensor< 2, dim, NumberType > > samples
Definition eom.hh:105
std::vector< double > kink_face_strengths
Definition eom.hh:112
std::unique_ptr< dealii::DoFHandler< dim > > dof_handler
Definition eom.hh:102
Evaluating an unread potential: the same three slots, all inert.
Definition eom.hh:144
UnusedPotential gradient
Definition eom.hh:145
UnusedPotential hessian
Definition eom.hh:145
UnusedPotential value
Definition eom.hh:145
Stand-in for a raw potential that a model has declared it does not read.
Definition eom.hh:140
dealii::Tensor< 2, dim, NumberType > jacobian
Definition eom.hh:403
dealii::Tensor< 1, dim, NumberType > value
Definition eom.hh:402
dealii::Point< dim > center
Definition eom.hh:401
double weight
Definition eom.hh:623
dealii::Tensor< 1, dim > normal
Definition eom.hh:624
bool is_rank_one
Definition eom.hh:503
double rank_one_confidence
Definition eom.hh:502
dealii::Tensor< 1, dim > normal
Definition eom.hh:504
double relative_jump
Definition eom.hh:501
double value
Definition eom.hh:1006
dealii::Tensor< 1, dim > gradient
Definition eom.hh:1007
dealii::Tensor< 2, dim > hessian
Definition eom.hh:1008
Vector< NumberType > rhs
Definition eom.hh:822
FullMatrix< NumberType > matrix
Definition eom.hh:821
std::vector< types::global_dof_index > dof_indices
Definition eom.hh:823
Vector< NumberType > cell_rhs
Definition eom.hh:848
std::vector< FaceCopy > face_data
Definition eom.hh:850
FaceCopy & new_face_data(const FEInterfaceValues< dim > &fe_interface_values)
Definition eom.hh:836
void reinit_cell(const Iterator &cell, const uint dofs_per_cell)
Definition eom.hh:826
FullMatrix< NumberType > cell_matrix
Definition eom.hh:847
std::vector< types::global_dof_index > cell_dof_indices
Definition eom.hh:849
std::array< std::vector< Vector< NumberType > >, 2 > solution_interface_values
Definition eom.hh:816
PotentialAssemblyScratch(const PotentialAssemblyScratch &scratch)
Definition eom.hh:787
FEValues< dim > potential_fe_values
Definition eom.hh:812
FEInterfaceValues< dim > potential_fe_interface_values
Definition eom.hh:814
std::vector< Vector< NumberType > > solution_values
Definition eom.hh:815
FEValues< dim > solution_fe_values
Definition eom.hh:811
FEInterfaceValues< dim > solution_fe_interface_values
Definition eom.hh:813
PotentialAssemblyScratch(const dealii::Mapping< dim > &mapping, const dealii::FiniteElement< dim > &solution_fe, const dealii::FiniteElement< dim > &potential_fe, const dealii::Quadrature< dim > &quadrature, const dealii::Quadrature< dim - 1 > &face_quadrature)
Definition eom.hh:769
dealii::Point< dim > point
Definition eom.hh:1001
double value
Definition eom.hh:1002
How much of an output frame went into reconstructing potentials.
Definition eom.hh:65
double seconds
Definition eom.hh:67
std::size_t solves
Definition eom.hh:66
double smoothing_length
Definition eom.hh:370
dealii::Quadrature< face_dim > face_quadrature
Definition eom.hh:327
const void * solution_dofs
Definition eom.hh:365
dealii::Quadrature< dim > quadrature
Definition eom.hh:326
bool describes(const dealii::Triangulation< dim > &tria, const dealii::DoFHandler< dim > &solution_dof_handler, const double smoothing, const uint potential_fe_degree) const
Whether this entry still describes the system that would be built now.
Definition eom.hh:339
std::size_t n_active_cells
Definition eom.hh:366
std::shared_ptr< dealii::DoFHandler< dim > > dof_handler
Definition eom.hh:321
std::size_t n_vertices
Definition eom.hh:368
std::shared_ptr< dealii::FiniteElement< dim > > finite_element
Definition eom.hh:320
uint solution_fe_degree
Definition eom.hh:369
const void * triangulation
Definition eom.hh:364
dealii::AffineConstraints< NumberType > constraints
Definition eom.hh:322
dealii::SparseDirectUMFPACK factorization
Definition eom.hh:325
std::map< dealii::types::global_dof_index, dealii::Point< dim > > support_points
Where each potential DoF sits; needed by the gauge choice and by every minimum search.
Definition eom.hh:329
dealii::SparsityPattern sparsity_pattern
Definition eom.hh:323
dealii::SparseMatrix< NumberType > matrix
Definition eom.hh:324
boost::signals2::scoped_connection connection
Definition eom.hh:371
void note_built_for(const dealii::Triangulation< dim > &tria, const dealii::DoFHandler< dim > &solution_dof_handler, const double smoothing)
Definition eom.hh:349