3template <
int dim,
int spacedim>
5 : mpi_communicator(comm)
6 , n_mpi_processes(Utilities::MPI::n_mpi_processes(comm))
7 , this_mpi_process(Utilities::MPI::this_mpi_process(comm))
8 , pcout(std::cout, this_mpi_process == 0)
9 , current_distance_threshold(1e-1)
10 , effective_distance_threshold(1e-1)
11 , current_potential(nullptr)
12 , current_epsilon(1.0)
13 , global_functional(0.0)
15 , covering_radius(0.0)
16 , min_target_density(0.0)
19 distance_function = [](
const Point<spacedim> x,
const Point<spacedim> y) {
return euclidean_distance<spacedim>(x, y); };
22template <
int dim,
int spacedim>
24 const std::string &distance_name_)
26 distance_name = distance_name_;
27 if (distance_name ==
"euclidean") {
28 pcout <<
"Using Euclidean distance function." << std::endl;
29 distance_function = [](
const Point<spacedim> x,
const Point<spacedim> y) {
return euclidean_distance<spacedim>(x, y); };
30 distance_function_gradient = [](
const Point<spacedim> x,
const Point<spacedim> y) {
return euclidean_distance_gradient<spacedim>(x, y); };
31 distance_function_exponential_map = [](
const Point<spacedim> x,
const Vector<double> v) {
return euclidean_distance_exp_map<spacedim>(x, v); };
33 }
else if (distance_name ==
"spherical") {
34 pcout <<
"Using Spherical distance function." << std::endl;
35 distance_function = [](
const Point<spacedim> x,
const Point<spacedim> y) {
return spherical_distance<spacedim>(x, y); };
36 distance_function_gradient = [](
const Point<spacedim> x,
const Point<spacedim> y) {
return spherical_distance_gradient<spacedim>(x, y); };
37 distance_function_exponential_map = [](
const Point<spacedim> x,
const Vector<double> v) {
return spherical_distance_exp_map<spacedim>(x, v); };
40 throw std::invalid_argument(
"Unknown distance function: " + distance_name);
44template <
int dim,
int spacedim>
46 const DoFHandler<dim, spacedim>& dof_handler,
47 const Mapping<dim, spacedim>& mapping,
48 const FiniteElement<dim, spacedim>& fe,
49 const LinearAlgebra::distributed::Vector<double, MemorySpace::Host>& source_density,
50 const unsigned int quadrature_order)
52 source_measure =
SourceMeasure(dof_handler, mapping, fe, source_density, quadrature_order);
55template <
int dim,
int spacedim>
57 const std::vector<Point<spacedim>>& target_points,
58 const Vector<double>& target_density)
60 target_measure =
TargetMeasure(target_points, target_density);
63template <
int dim,
int spacedim>
68 current_params = params;
69 current_epsilon = params.
epsilon;
72template <
int dim,
int spacedim>
75 if (!source_measure.dof_handler) {
76 pcout <<
"Error: Source measure not set up" << std::endl;
79 if (target_measure.points.empty()) {
80 pcout <<
"Error: Target measure not set up" << std::endl;
83 if (target_measure.density.size() != target_measure.points.size()) {
84 pcout <<
"Error: Target density size mismatch" << std::endl;
90template <
int dim,
int spacedim>
92 Vector<double>& potentials,
98 source_measure = source;
99 target_measure = target;
102 solve(potentials, params);
105template <
int dim,
int spacedim>
107 Vector<double>& potentials,
110 if (!validate_measures()) {
111 throw std::runtime_error(
"Invalid measures configuration");
115 current_params = params;
116 current_epsilon = params.
epsilon;
119 unsigned int n_threads = params.
n_threads;
120 if (n_threads == 0) {
121 n_threads = std::max(1U, MultithreadInfo::n_cores() / n_mpi_processes);
123 MultithreadInfo::set_thread_limit(n_threads);
125 pcout <<
"Parallel Configuration:" << std::endl
126 <<
" MPI Processes: " << n_mpi_processes
127 <<
" (Current rank: " << this_mpi_process <<
")" << std::endl
128 <<
" Threads per process: " << n_threads << std::endl
129 <<
" Total parallel units: " << n_threads * n_mpi_processes << std::endl;
132 covering_radius = compute_covering_radius() * 1.1;
133 min_target_density = *std::min_element(target_measure.density.begin(), target_measure.density.end());
135 pcout <<
"Covering radius (R0): " << covering_radius << std::endl
136 <<
" (Maximum distance from any source cell center to the nearest target point)" << std::endl;
140 pcout <<
"Small entropy detected (ε = " << params.
epsilon <<
")" << std::endl;
141 pcout <<
" Log-Sum-Exp trick: " << (params.
use_log_sum_exp_trick ?
"enabled" :
"disabled") << std::endl;
143 pcout <<
" \033[1;33mWARNING: Using very small entropy without Log-Sum-Exp trick may cause numerical instability\033[0m" << std::endl;
148 if (potentials.size() != target_measure.points.size()) {
149 potentials.reinit(target_measure.points.size());
153 gradient.reinit(potentials.size());
157 double solver_ctrl_tolerance;
158 if (use_componentwise) {
159 solver_ctrl_tolerance = std::numeric_limits<double>::min();
161 solver_ctrl_tolerance = params.
tolerance;
164 solver_control = std::make_unique<VerboseSolverControl>(
166 solver_ctrl_tolerance,
172 solver_control->log_history(
false);
173 solver_control->log_result(
false);
177 AssertThrow(verbose_control !=
nullptr, ExcInternalError());
179 verbose_control->set_gradient(gradient);
181 if (use_componentwise) {
182 verbose_control->set_target_measure(target_measure.density, params.
tolerance);
190 SolverBFGS<Vector<double>> solver(*solver_control);
191 current_potential = &potentials;
194 [
this](
const Vector<double>& w, Vector<double>& grad) {
195 return this->evaluate_functional(w, grad);
203 <<
" Time taken: " << timer.wall_time() <<
" seconds" << std::endl
204 <<
" Iterations: " << solver_control->last_step() << std::endl
205 <<
" Final function value: " << solver_control->last_value() <<
Color::reset << std::endl;
207 }
catch (SolverControl::NoConvergence& exc) {
208 pcout <<
"Warning: Optimization did not converge" << std::endl
209 <<
" Iterations: " << exc.last_step << std::endl
210 <<
" Residual: " << exc.last_residual << std::endl;
215 current_potential =
nullptr;
218template <
int dim,
int spacedim>
220 const Vector<double>& potentials,
221 Vector<double>& gradient_out)
224 current_potential = &potentials;
225 current_epsilon = current_params.epsilon;
228 compute_distance_threshold();
231 global_functional = 0.0;
234 double local_process_functional = 0.0;
235 Vector<double> local_process_gradient(target_measure.points.size());
236 double local_process_C_sum = 0.0;
238 if (current_params.verbose_output) {
239 pcout <<
"Using distance threshold: " << current_distance_threshold
240 <<
" (Effective: " << effective_distance_threshold <<
")" << std::endl;
245 bool use_simplex = (
dynamic_cast<const FE_SimplexP<dim>*
>(&*source_measure.fe) !=
nullptr);
248 std::unique_ptr<Quadrature<dim>> quadrature;
250 quadrature = std::make_unique<QGaussSimplex<dim>>(source_measure.quadrature_order);
252 quadrature = std::make_unique<QGauss<dim>>(source_measure.quadrature_order);
257 *source_measure.mapping,
259 CopyData copy_data(target_measure.points.size());
262 FilteredIterator<typename DoFHandler<dim, spacedim>::active_cell_iterator>
263 begin_filtered(IteratorFilters::LocallyOwnedCell(),
264 source_measure.dof_handler->begin_active()),
265 end_filtered(IteratorFilters::LocallyOwnedCell(),
266 source_measure.dof_handler->end());
268 auto function_call = [
this](
270 const Point<spacedim> &x,
271 const std::vector<std::size_t> &cell_target_indices,
272 const std::vector<double> &exp_terms,
273 const std::vector<double> &target_densities,
274 const double &density_value,
276 const double &total_sum_exp,
277 const double &max_exponent,
278 const double ¤t_epsilon)
282 if (current_params.use_log_sum_exp_trick) {
284 (max_exponent + std::log(total_sum_exp)) * JxW;
287 std::log(total_sum_exp) * JxW;
289 const double scale = density_value * JxW / total_sum_exp;
292 for (
size_t i = 0; i < cell_target_indices.size(); ++i) {
293 if (exp_terms[i] > 0.0) {
303 [
this, &function_call](
const typename DoFHandler<dim, spacedim>::active_cell_iterator& cell,
306 this->local_assemble(cell, scratch, copy, function_call);
308 [
this, &local_process_functional, &local_process_gradient, &local_process_C_sum](
const CopyData& copy) {
317 global_functional = Utilities::MPI::sum(local_process_functional, mpi_communicator);
319 Utilities::MPI::sum(local_process_gradient, mpi_communicator, gradient);
320 C_global = Utilities::MPI::sum(local_process_C_sum, mpi_communicator);
323 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
324 for (
unsigned int i = 0; i < target_measure.points.size(); ++i) {
325 global_functional -= potentials[i] * target_measure.density[i];
326 gradient[i] -= target_measure.density[i];
331 global_functional = Utilities::MPI::broadcast(mpi_communicator, global_functional, 0);
332 gradient = Utilities::MPI::broadcast(mpi_communicator, gradient, 0);
335 gradient_out = gradient;
337 if (current_params.verbose_output) {
338 pcout <<
"Functional evaluation completed:" << std::endl;
339 pcout <<
" Function value: " << global_functional << std::endl;
340 pcout <<
" C_global: " << C_global << std::endl;
343 if (current_potential !=
nullptr && current_potential->size() == target_measure.points.size()) {
344 double geom_radius_bound = compute_geometric_radius_bound(*current_potential, current_epsilon, current_params.tau);
347 double max_pot = *std::max_element(current_potential->begin(), current_potential->end());
348 double min_tgt_density = min_target_density > 0.0 ?
350 *std::min_element(target_measure.density.begin(), target_measure.density.end());
351 double sq_threshold = -2.0 * current_epsilon * std::log(current_params.tau/min_tgt_density) + 2.0 * max_pot;
352 double pointwise_bound = std::sqrt(std::max(0.0, sq_threshold));
355 double integral_radius_bound = compute_integral_radius_bound(
363 pcout <<
" Current distance threshold: " << current_distance_threshold
364 <<
" (using: " << current_params.distance_threshold_type <<
")" << std::endl
365 <<
" Pointwise bound (eps_machine=" << current_params.tau <<
"): " << pointwise_bound << std::endl
366 <<
" Integral bound (C_global=" << C_global <<
", τ=" << current_params.tau <<
"): " << integral_radius_bound << std::endl
367 <<
" Geometric bound (τ=" << current_params.tau <<
"): " << geom_radius_bound
368 <<
" (ratio to pointwise: " << (pointwise_bound > 1e-9 ? geom_radius_bound/pointwise_bound : 0.0) <<
")" << std::endl;
372 }
catch (
const std::exception& e) {
373 pcout <<
"Error in functional evaluation: " << e.what() << std::endl;
377 return global_functional;
380template <
int dim,
int spacedim>
383 if (current_potential ==
nullptr) {
384 current_distance_threshold = std::numeric_limits<double>::max();
385 effective_distance_threshold = std::numeric_limits<double>::max();
390 double computed_threshold = 0.0;
391 std::string used_method_for_log;
393 if (current_params.distance_threshold_type ==
"integral") {
394 computed_threshold = compute_integral_radius_bound(
401 computed_threshold = std::max(computed_threshold, covering_radius);
402 used_method_for_log =
"integral (C_global based)";
403 }
else if (current_params.distance_threshold_type ==
"geometric") {
405 computed_threshold = compute_geometric_radius_bound(*current_potential, current_epsilon, current_params.tau);
406 used_method_for_log =
"geometric (covering radius based)";
408 double max_potential = *std::max_element(current_potential->begin(), current_potential->end());
409 double current_min_target_density = min_target_density > 0.0 ?
411 *std::min_element(target_measure.density.begin(), target_measure.density.end());
413 if (current_min_target_density <= 0 || current_params.tau <=0) {
414 computed_threshold = std::numeric_limits<double>::max();
416 double squared_threshold = -2.0 * current_epsilon *
417 std::log(current_params.tau/current_min_target_density) + 2.0 * max_potential;
418 computed_threshold = std::sqrt(std::max(0.0, squared_threshold));
420 used_method_for_log =
"pointwise (tau based)";
423 if (current_params.verbose_output) {
424 pcout <<
"Computed distance threshold using " << used_method_for_log <<
": " << computed_threshold << std::endl;
427 double new_proposed_effective_threshold = computed_threshold * 1.1;
429 current_distance_threshold = computed_threshold;
432template <
int dim,
int spacedim>
435 namespace bgi = boost::geometry::index;
437 if (!validate_measures()) {
438 throw std::runtime_error(
"Invalid measures configuration for computing covering radius");
441 double max_min_distance = 0.0;
444 FilteredIterator<typename DoFHandler<dim, spacedim>::active_cell_iterator>
445 begin_filtered(IteratorFilters::LocallyOwnedCell(),
446 source_measure.dof_handler->begin_active()),
447 end_filtered(IteratorFilters::LocallyOwnedCell(),
448 source_measure.dof_handler->end());
450 std::vector<double> local_min_distances;
453 for (
auto cell = begin_filtered; cell != end_filtered; ++cell) {
454 const Point<spacedim>& cell_center = cell->center();
457 std::vector<IndexedPoint> nearest_results;
458 target_measure.rtree.query(bgi::nearest(cell_center, 1), std::back_inserter(nearest_results));
460 if (!nearest_results.empty()) {
461 const Point<spacedim>& nearest_point = nearest_results[0].first;
462 const double distance = (nearest_point - cell_center).norm();
463 local_min_distances.push_back(distance);
468 if (!local_min_distances.empty()) {
469 max_min_distance = *std::max_element(local_min_distances.begin(), local_min_distances.end());
473 return Utilities::MPI::max(max_min_distance, mpi_communicator);
476template <
int dim,
int spacedim>
478 const Vector<double>& potentials,
482 double current_functional_val)
const
490 double max_potential = *std::max_element(potentials.begin(), potentials.end());
492 double abs_functional_val = std::abs(current_functional_val);
493 const double safety_value = 1e-10;
494 if (abs_functional_val < safety_value) {
495 abs_functional_val = safety_value;
498 double log_numerator = epsilon * C_value;
499 double log_denominator = tolerance * abs_functional_val;
503 double log_argument = log_numerator / log_denominator;
505 double radius_squared = 2.0 * max_potential + 2.0 * epsilon * std::log(log_argument);
507 return std::sqrt(std::max(0.0, radius_squared));
510template <
int dim,
int spacedim>
512 const Vector<double>& potentials,
513 const double epsilon,
514 const double tolerance)
const
516 if (!validate_measures() || potentials.size() != target_measure.points.size()) {
517 throw std::runtime_error(
"Invalid configuration for computing geometric radius bound");
522 double r0 = covering_radius > 0.0 ? covering_radius : compute_covering_radius();
525 double min_potential = *std::min_element(potentials.begin(), potentials.end());
526 double max_potential = *std::max_element(potentials.begin(), potentials.end());
527 double potential_range = max_potential - min_potential;
530 double min_density = min_target_density > 0.0 ?
532 *std::min_element(target_measure.density.begin(), target_measure.density.end());
535 double functional_value = std::abs(global_functional);
536 const double safety_value = 1e-10;
537 if (functional_value < safety_value) {
538 functional_value = safety_value;
543 double log_term = std::log(epsilon / (min_density * tolerance * functional_value));
544 double radius_squared = r0 * r0 + 2.0 * potential_range + 2.0 * epsilon * log_term;
547 if (radius_squared < r0 * r0) {
548 radius_squared = r0 * r0;
551 return std::sqrt(radius_squared);
554template <
int dim,
int spacedim>
556 const Point<spacedim>& query_point)
const
558 namespace bgi = boost::geometry::index;
559 std::vector<std::size_t> indices;
562 for (
const auto& indexed_point : target_measure.rtree |
563 bgi::adaptors::queried(bgi::satisfies([&](
const IndexedPoint& p) {
564 return distance_function(p.first, query_point) <= current_distance_threshold;
567 indices.push_back(indexed_point.second);
573template <
int dim,
int spacedim>
576 return solver_control ? solver_control->last_step() : 0;
579template <
int dim,
int spacedim>
582 return solver_control && solver_control->last_check() == SolverControl::success;
585template <
int dim,
int spacedim>
587 const Vector<double>& potentials,
588 std::vector<Point<spacedim>>& barycenters_out,
591 if (!validate_measures()) {
592 throw std::runtime_error(
"Invalid measures configuration");
596 double solver_tolerance = params.
tolerance;
603 unsigned int n_iter = 0;
607 current_potential = &potentials;
609 if (distance_name ==
"euclidean") {
610 compute_weighted_barycenters_euclidean(
611 *current_potential, barycenters_out);
613 }
else if (distance_name ==
"spherical")
615 for (n_iter = 0; n_iter < max_iterations; ++n_iter)
619 barycenters_grads = std::vector<Vector<double>>(target_measure.points.size(), Vector<double>(spacedim));
622 compute_weighted_barycenters_non_euclidean(
623 *current_potential, barycenters_grads, barycenters_out);
626 double l2_norm = barycenters_gradients.l2_norm();
628 if (l2_norm < solver_tolerance) {
629 pcout <<
"Iteration " <<
CYAN << n_iter + 1 <<
RESET
630 <<
" - L-2 gradient norm: " <<
Color::green << l2_norm <<
" < " << solver_tolerance <<
RESET << std::endl;
634 pcout <<
"Iteration " <<
CYAN << n_iter + 1 <<
RESET
635 <<
" - L-2 gradient norm: " <<
Color::yellow << l2_norm <<
" > " << solver_tolerance <<
RESET << std::endl;
637 for (
unsigned int i=0; i<target_measure.points.size();++i)
639 barycenters_out[i] = distance_function_exponential_map(barycenters_out[i], barycenters_grads[i]);
648 <<
" Time taken: " << timer.wall_time() <<
" seconds" << std::endl
649 <<
" Iterations: " << n_iter+1 << std::endl
650 <<
" Distance type: " << distance_name << std::endl <<
Color::reset;
652 }
catch (SolverControl::NoConvergence& exc) {
653 pcout <<
"Warning: Barycenters evaluation did not converge" << std::endl
654 <<
" Iterations: " << exc.last_step << std::endl
655 <<
" Residual: " << exc.last_residual << std::endl;
660 current_potential =
nullptr;
663template <
int dim,
int spacedim>
665 const Vector<double>& potentials,
666 std::vector<Vector<double>>& barycenters_gradients_out,
667 std::vector<Point<spacedim>>& barycenters_out
671 current_potential = &potentials;
672 current_epsilon = current_params.epsilon;
674 barycenters_gradients.reinit(spacedim*target_measure.points.size());
675 Vector<double> local_process_barycenters(spacedim*target_measure.points.size());
678 compute_distance_threshold();
679 if (current_params.verbose_output) {
680 pcout <<
"Using distance threshold: " << current_distance_threshold
681 <<
" (Effective: " << effective_distance_threshold <<
")" << std::endl;
686 bool use_simplex = (
dynamic_cast<const FE_SimplexP<dim>*
>(&*source_measure.fe) !=
nullptr);
689 std::unique_ptr<Quadrature<dim>> quadrature;
691 quadrature = std::make_unique<QGaussSimplex<dim>>(source_measure.quadrature_order);
693 quadrature = std::make_unique<QGauss<dim>>(source_measure.quadrature_order);
698 *source_measure.mapping,
700 CopyData copy_data(target_measure.points.size());
703 FilteredIterator<typename DoFHandler<dim, spacedim>::active_cell_iterator>
704 begin_filtered(IteratorFilters::LocallyOwnedCell(),
705 source_measure.dof_handler->begin_active()),
706 end_filtered(IteratorFilters::LocallyOwnedCell(),
707 source_measure.dof_handler->end());
710 auto function_call = [
this, &barycenters_out](
712 const Point<spacedim> &x,
713 const std::vector<std::size_t> &cell_target_indices,
714 const std::vector<double> &exp_terms,
715 const std::vector<double> &target_densities,
716 const double &density_value,
718 const double &total_sum_exp,
719 const double &max_exponent,
720 const double ¤t_epsilon)
722 const double scale = density_value * JxW / total_sum_exp;
725 for (
size_t i = 0; i < exp_terms.size(); ++i) {
726 if (exp_terms[i] > 0.0) {
727 auto v = distance_function_gradient(barycenters_out[cell_target_indices[i]], x);
728 for (
unsigned int d = 0; d < spacedim; ++d)
729 copy.
barycenters_values[spacedim*cell_target_indices[i] + d] += scale * (exp_terms[i]) * v[d];
738 [
this, &function_call](
const typename DoFHandler<dim, spacedim>::active_cell_iterator& cell,
741 this->local_assemble(
742 cell, scratch, copy, function_call);
744 [
this, &local_process_barycenters](
const CopyData& copy) {
751 Utilities::MPI::sum(local_process_barycenters, mpi_communicator, barycenters_gradients);
755 barycenters_gradients_out.resize(target_measure.points.size());
756 for (
unsigned int i = 0; i < target_measure.points.size(); ++i) {
757 for (
unsigned int d = 0; d < spacedim; ++d) {
759 barycenters_gradients_out[i][d] = -barycenters_gradients[spacedim * i + d];
763 }
catch (
const std::exception& e) {
764 pcout <<
"Error in functional evaluation: " << e.what() << std::endl;
769template <
int dim,
int spacedim>
771 const typename DoFHandler<dim, spacedim>::active_cell_iterator& cell,
775 const Point<spacedim>&,
776 const std::vector<std::size_t>&,
777 const std::vector<double>&,
778 const std::vector<double>&,
783 const double&)> function_call)
785 if (!cell->is_locally_owned())
789 const std::vector<Point<spacedim>>& q_points = scratch.
fe_values.get_quadrature_points();
797 const unsigned int n_q_points = q_points.size();
798 const double epsilon_inv = 1.0 / current_epsilon;
799 const bool use_log_sum_exp = current_params.use_log_sum_exp_trick;
801 std::vector<std::size_t> cell_target_indices = find_nearest_target_points(cell->center());
802 if (cell_target_indices.empty())
return;
804 const unsigned int n_target_points = cell_target_indices.size();
806 std::vector<Point<spacedim>> target_positions(n_target_points);
807 std::vector<double> target_densities(n_target_points);
808 std::vector<double> potential_values(n_target_points);
810 for (
size_t i = 0; i < n_target_points; ++i) {
811 const size_t idx = cell_target_indices[i];
812 target_positions[i] = target_measure.points[idx];
813 target_densities[i] = target_measure.density[idx];
814 potential_values[i] = (*current_potential)[idx];
817 for (
unsigned int q = 0; q < n_q_points; ++q) {
818 const Point<spacedim>& x = q_points[q];
820 const double JxW = scratch.
fe_values.JxW(q);
822 double total_sum_exp = 0.0;
823 double max_exponent = -std::numeric_limits<double>::max();
824 std::vector<double> exp_terms(n_target_points);
826 if (use_log_sum_exp) {
828 #pragma omp simd reduction(max:max_exponent)
829 for (
size_t i = 0; i < n_target_points; ++i) {
830 const double local_dist2 = std::pow(distance_function(x, target_positions[i]), 2);
831 const double exponent = (potential_values[i] - 0.5 * local_dist2) * epsilon_inv;
832 max_exponent = std::max(max_exponent, exponent);
836 #pragma omp simd reduction(+:total_sum_exp)
837 for (
size_t i = 0; i < n_target_points; ++i) {
838 const double local_dist2 = std::pow(distance_function(x, target_positions[i]), 2);
839 const double shifted_exp = std::exp((potential_values[i] - 0.5 * local_dist2) * epsilon_inv - max_exponent);
840 exp_terms[i] = target_densities[i] * shifted_exp;
841 total_sum_exp += exp_terms[i];
845 #pragma omp simd reduction(+:total_sum_exp)
846 for (
size_t i = 0; i < n_target_points; ++i) {
847 const double local_dist2 = std::pow(distance_function(x, target_positions[i]), 2);
848 exp_terms[i] = target_densities[i] *
849 std::exp((potential_values[i] - 0.5 * local_dist2) * epsilon_inv);
850 total_sum_exp += exp_terms[i];
854 if (total_sum_exp <= 0.0)
continue;
871template <
int dim,
int spacedim>
873 const Vector<double>& potentials,
874 std::vector<Point<spacedim>>& barycenters_out)
877 current_potential = &potentials;
878 current_epsilon = current_params.epsilon;
880 barycenters.reinit(spacedim*target_measure.points.size());
881 Vector<double> local_process_barycenters(spacedim*target_measure.points.size());
884 compute_distance_threshold();
885 if (current_params.verbose_output) {
886 pcout <<
"Using distance threshold: " << current_distance_threshold
887 <<
" (Effective: " << effective_distance_threshold <<
")" << std::endl;
892 bool use_simplex = (
dynamic_cast<const FE_SimplexP<dim>*
>(&*source_measure.fe) !=
nullptr);
895 std::unique_ptr<Quadrature<dim>> quadrature;
897 quadrature = std::make_unique<QGaussSimplex<dim>>(source_measure.quadrature_order);
899 quadrature = std::make_unique<QGauss<dim>>(source_measure.quadrature_order);
904 *source_measure.mapping,
906 CopyData copy_data(target_measure.points.size());
909 FilteredIterator<typename DoFHandler<dim, spacedim>::active_cell_iterator>
910 begin_filtered(IteratorFilters::LocallyOwnedCell(),
911 source_measure.dof_handler->begin_active()),
912 end_filtered(IteratorFilters::LocallyOwnedCell(),
913 source_measure.dof_handler->end());
916 auto function_call = [
this](
918 const Point<spacedim> &x,
919 const std::vector<std::size_t> &cell_target_indices,
920 const std::vector<double> &exp_terms,
921 const std::vector<double> &target_densities,
922 const double &density_value,
924 const double &total_sum_exp,
925 const double &max_exponent,
926 const double ¤t_epsilon)
928 const double scale = density_value * JxW / total_sum_exp;
931 for (
size_t i = 0; i < exp_terms.size(); ++i) {
932 if (exp_terms[i] > 0.0) {
933 for (
unsigned int d = 0; d < spacedim; ++d) {
934 copy.
barycenters_values[spacedim*cell_target_indices[i] + d] += scale * (exp_terms[i]) * x[d];
944 [
this, &function_call](
const typename DoFHandler<dim, spacedim>::active_cell_iterator& cell,
947 this->local_assemble(cell, scratch, copy, function_call);
949 [
this, &local_process_barycenters](
const CopyData& copy) {
956 Utilities::MPI::sum(local_process_barycenters, mpi_communicator, barycenters);
960 barycenters_out.resize(target_measure.points.size());
961 for (
unsigned int i = 0; i < target_measure.points.size(); ++i) {
962 for (
unsigned int d = 0; d < spacedim; ++d) {
963 barycenters_out[i][d] = barycenters[spacedim * i + d]/target_measure.density[i];
967 }
catch (
const std::exception& e) {
968 pcout <<
"Error in functional evaluation: " << e.what() << std::endl;
973template <
int dim,
int spacedim>
975 const DoFHandler<dim, spacedim> &dof_handler,
976 const Mapping<dim, spacedim> &mapping,
977 const Vector<double> &potential,
978 const std::vector<unsigned int> &potential_indices,
979 std::vector<LinearAlgebra::distributed::Vector<double, MemorySpace::Host>> &conditioned_densities)
981 std::cout <<
"Current epsilon: " << current_epsilon << std::endl;
982 auto locally_owned_dofs = dof_handler.locally_owned_dofs();
983 conditioned_densities.resize(potential_indices.size());
985 std::map<types::global_dof_index, Point<spacedim>> sp;
986 DoFTools::map_dofs_to_support_points(
987 mapping, dof_handler, sp);
989 for (
unsigned int idensity = 0; idensity < conditioned_densities.size(); ++idensity)
990 conditioned_densities[idensity].reinit(locally_owned_dofs, mpi_communicator);
992 double epsilon_inv = 1.0 / current_epsilon;
994 for (
auto idx: locally_owned_dofs)
996 std::vector<std::size_t> cell_target_indices = find_nearest_target_points(sp[idx]);
998 std::vector<double> exp(potential.size(), 0.0);
999 double total_sum_exp = 0;
1000 double max_exponent = -std::numeric_limits<double>::max();
1002 #pragma omp simd reduction(max:max_exponent)
1003 for (
unsigned int i = 0; i < cell_target_indices.size(); ++i)
1005 const size_t tidx = cell_target_indices[i];
1006 const double exponent = (potential[tidx]-0.5*
1010 target_measure.points[tidx]), 2))*epsilon_inv;
1011 max_exponent = std::max(max_exponent, exponent);
1014 #pragma omp simd reduction(+:total_sum_exp)
1015 for (
unsigned int i = 0; i < cell_target_indices.size(); ++i)
1017 const size_t tidx = cell_target_indices[i];
1018 exp[tidx] = std::exp((potential[tidx]-0.5*
1022 target_measure.points[tidx]), 2))*epsilon_inv-max_exponent);
1023 total_sum_exp += target_measure.density[tidx] * exp[tidx];
1026 if (total_sum_exp > 0.0)
1028 for (
unsigned int idensity = 0; idensity < potential_indices.size(); ++idensity)
1030 bool index_found = std::find(cell_target_indices.begin(),
1031 cell_target_indices.end(),
1032 potential_indices[idensity]) != cell_target_indices.end();
1035 conditioned_densities[idensity][idx] = (*source_measure.density)[idx] * (exp[potential_indices[idensity]]/total_sum_exp);
1041 for (
unsigned int idensity = 0; idensity < conditioned_densities.size(); ++idensity)
1043 conditioned_densities[idensity].compress(VectorOperation::insert);
A verbose solver control class that prints the progress of the solver.
A solver for semi-discrete optimal transport problems.
unsigned int get_last_iteration_count() const
Returns the number of iterations of the last solve.
double compute_geometric_radius_bound(const Vector< double > &potentials, const double epsilon, const double tolerance) const
Computes the geometric radius bound for truncating quadrature rules.
bool validate_measures() const
void local_assemble(const typename DoFHandler< dim, spacedim >::active_cell_iterator &cell, ScratchData &scratch, CopyData ©, std::function< void(CopyData &, const Point< spacedim > &, const std::vector< std::size_t > &, const std::vector< double > &, const std::vector< double > &, const double &, const double &, const double &, const double &, const double &)> function_call)
std::function< double(const Point< spacedim > &, const Point< spacedim > &)> distance_function
The distance function.
std::pair< Point< spacedim >, std::size_t > IndexedPoint
void get_potential_conditioned_density(const DoFHandler< dim, spacedim > &dof_handler, const Mapping< dim, spacedim > &mapping, const Vector< double > &potential, const std::vector< unsigned int > &potential_indices, std::vector< LinearAlgebra::distributed::Vector< double, MemorySpace::Host > > &conditioned_densities)
Computes the conditional density of the source measure given a potential.
void set_distance_function(const std::string &distance_name)
Sets the distance function to be used by the solver.
void setup_target(const std::vector< Point< spacedim > > &target_points, const Vector< double > &target_density)
Sets up the target measure for the solver.
void compute_weighted_barycenters_non_euclidean(const Vector< double > &potentials, std::vector< Vector< double > > &barycenters_gradients_out, std::vector< Point< spacedim > > &barycenters_out)
void solve(Vector< double > &potential, const SotParameterManager::SolverParameters ¶ms)
Solves the optimal transport problem.
double compute_covering_radius() const
Computes the covering radius of the target measure with respect to the source domain.
void compute_weighted_barycenters_euclidean(const Vector< double > &potentials, std::vector< Point< spacedim > > &barycenters_out)
std::vector< std::size_t > find_nearest_target_points(const Point< spacedim > &query_point) const
SotSolver(const MPI_Comm &comm)
Constructor for the SotSolver.
void setup_source(const DoFHandler< dim, spacedim > &dof_handler, const Mapping< dim, spacedim > &mapping, const FiniteElement< dim, spacedim > &fe, const LinearAlgebra::distributed::Vector< double, MemorySpace::Host > &source_density, const unsigned int quadrature_order)
Sets up the source measure for the solver.
void evaluate_weighted_barycenters(const Vector< double > &potentials, std::vector< Point< spacedim > > &barycenters_out, const SotParameterManager::SolverParameters ¶ms)
Evaluates the weighted barycenters of the power cells.
void compute_distance_threshold() const
bool get_convergence_status() const
Returns the convergence status of the last solve.
double evaluate_functional(const Vector< double > &potential, Vector< double > &gradient_out)
Evaluates the dual functional and its gradient.
void configure(const SotParameterManager::SolverParameters ¶ms)
Configures the solver with the given parameters.
double compute_integral_radius_bound(const Vector< double > &potentials, double epsilon, double tolerance, double C_value, double current_functional_val) const
double tolerance
Convergence tolerance.
bool use_log_sum_exp_trick
Enable log-sum-exp trick for numerical stability with small entropy.
unsigned int n_threads
Number of threads (0 = auto)
std::string solver_control_type
Type of solver control to use (l1norm/componentwise)
bool verbose_output
Enable detailed solver output.
unsigned int max_iterations
Maximum number of solver iterations.
double epsilon
Entropy regularization parameter.
A struct to hold copy data for parallel assembly.
double functional_value
The value of the functional on the current cell.
Vector< double > barycenters_values
The barycenter values for the current cell.
Vector< double > gradient_values
The local contribution to the gradient.
double local_C_sum
The sum of the scale terms for this cell.
A struct to hold scratch data for parallel assembly.
FEValues< dim, spacedim > fe_values
FEValues object for the current cell.
std::vector< double > density_values
The density values at the quadrature points of the current cell.
A struct to hold all the necessary information about the source measure.
A struct to hold all the necessary information about the target measure.