SemiDiscreteOT 1.0
Semi-Discrete Optimal Transport Library
Loading...
Searching...
No Matches
SemiDiscreteOT.cc
Go to the documentation of this file.
1// SemiDiscreteOT.cc
3namespace fs = std::filesystem;
4
5using namespace dealii;
6
7template <int dim, int spacedim>
9 : mpi_communicator(comm)
10 , n_mpi_processes(Utilities::MPI::n_mpi_processes(comm))
11 , this_mpi_process(Utilities::MPI::this_mpi_process(comm))
12 , pcout(std::cout, Utilities::MPI::this_mpi_process(comm) == 0)
13 , param_manager(comm)
14 , source_params(param_manager.source_params)
15 , target_params(param_manager.target_params)
16 , solver_params(param_manager.solver_params)
17 , multilevel_params(param_manager.multilevel_params)
18 , power_diagram_params(param_manager.power_diagram_params)
19 , transport_map_params(param_manager.transport_map_params)
20 , selected_task(param_manager.selected_task)
21 , io_coding(param_manager.io_coding)
22 , source_mesh(comm)
23 , target_mesh()
24 , dof_handler_source(source_mesh)
25 , dof_handler_target(target_mesh)
26 , mesh_manager(std::make_unique<MeshManager<dim, spacedim>>(comm))
27 , sot_solver(std::make_unique<SotSolver<dim, spacedim>>(comm))
28{
29}
30
31template <int dim, int spacedim>
33{
34 config_func(param_manager);
35 pcout << "Solver configured programmatically." << std::endl;
36 param_manager.print_parameters();
37
38 if (solver_params.use_epsilon_scaling)
39 {
40 epsilon_scaling_handler = std::make_unique<EpsilonScalingHandler>(
41 mpi_communicator,
42 solver_params.epsilon,
43 solver_params.epsilon_scaling_factor,
44 solver_params.epsilon_scaling_steps);
45 }
46
47 is_setup_programmatically_ = true;
48}
49
50template <int dim, int spacedim>
52 Triangulation<dim, spacedim>& tria,
53 const DoFHandler<dim, spacedim>& dh,
54 const Vector<double>& density,
55 const std::string& name)
56{
57 pcout << "Setting up source measure from standard dealii objects (simplified API)..." << std::endl;
58
59 // Store the source mesh name for later use
60 if (!name.empty())
61 source_mesh_name = name;
62
63 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
64 {
65 std::string mesh_directory = "output/data_mesh";
66 mesh_manager->write_mesh(tria,
67 mesh_directory + "/" + source_mesh_name,
68 std::vector<std::string>{"vtk", "msh"});
69 }
70
71 // Create distributed triangulation from serial triangulation
72 pcout << "Converting serial triangulation to distributed triangulation..." << std::endl;
73
74 // Partition the serial source mesh using z-order curve
75 GridTools::partition_triangulation_zorder(n_mpi_processes, tria);
76
77 // Convert serial source mesh to fullydistributed without multigrid hierarchy
78 auto construction_data = TriangulationDescription::Utilities::create_description_from_triangulation(
79 tria, mpi_communicator,
80 TriangulationDescription::Settings::default_setting);
81 source_mesh.create_triangulation(construction_data);
82
83 // Set up finite elements on the distributed mesh
84 auto [fe, map] = Utils::create_fe_and_mapping_for_mesh<dim, spacedim>(source_mesh);
85 fe_system = std::move(fe);
86 mapping = std::move(map);
88 // Set up DoFHandler on distributed mesh
89 dof_handler_source.clear();
90 dof_handler_source.reinit(source_mesh);
91 dof_handler_source.distribute_dofs(*fe_system);
92
93 // Set up distributed vectors
94 IndexSet locally_owned_dofs = dof_handler_source.locally_owned_dofs();
95 IndexSet locally_relevant_dofs;
96 DoFTools::extract_locally_relevant_dofs(dof_handler_source, locally_relevant_dofs);
97
98 source_density.reinit(locally_owned_dofs, locally_relevant_dofs, mpi_communicator);
99
100 // Convert serial density to distributed density
101 pcout << "Converting serial density vector to distributed format..." << std::endl;
102 AssertDimension(density.size(), dof_handler_source.n_dofs());
103
104 // Copy density values to distributed vector
105 for (const auto& dof_index : locally_owned_dofs)
107 source_density[dof_index] = density[dof_index];
108 }
109 source_density.update_ghost_values();
110
111 // Normalize the density
112 normalize_density(source_density);
113
114 pcout << "Distributed source mesh has " << source_mesh.n_active_cells()
115 << " active cells and " << dof_handler_source.n_dofs() << " DoFs" << std::endl;
116
117 // Store fine-grain data for multilevel operations if needed
118 if (multilevel_params.source_enabled)
119 {
120 pcout << "Storing fine-grain source data for multilevel interpolation." << std::endl;
121 initial_fine_dof_handler = std::make_unique<DoFHandler<dim, spacedim>>(tria);
122 initial_fine_dof_handler->distribute_dofs(dh.get_fe());
123 initial_fine_density = std::make_unique<Vector<double>>(density);
124 }
125
126 is_setup_programmatically_ = true;
127 pcout << "Source measure setup from standard objects complete." << std::endl;
128}
129
130template <int dim, int spacedim>
132 const std::vector<Point<spacedim>>& points,
133 const Vector<double>& weights)
134{
135 pcout << "Setting up target measure from in-memory objects..." << std::endl;
136 AssertDimension(points.size(), weights.size());
137
138 target_points = points;
139 target_density = weights;
140
141 // Normalize density
142 double sum_weights = target_density.l1_norm();
143 if (sum_weights > 0)
144 target_density /= sum_weights;
145
146 sot_solver->setup_target(target_points, target_density);
147
148 // Save target points and density to files
149 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
150 {
151 // Create output directory if it doesn't exist
152 std::string output_dir_points = "output/data_points";
153 fs::create_directories(output_dir_points);
154
155 // Save points to file
156 Utils::write_vector(target_points, output_dir_points + "/target_points", io_coding);
157
158 std::string output_dir_density = "output/data_density";
159 fs::create_directories(output_dir_density);
160
161 // Save density to file
162 std::string density_file = output_dir_density + "/target_density";
163 std::vector<double> output_density_values(target_density.begin(), target_density.end());
164 Utils::write_vector(output_density_values, density_file, io_coding);
165
166 pcout << "Target points saved to " << output_dir_points << "/target_points" << std::endl;
167 pcout << "Target density saved to " << density_file << std::endl;
168 }
169
170 pcout << "Target measure setup complete with " << target_points.size() << " points." << std::endl;
172
173
174template <int dim, int spacedim>
176{
177 pcout << "Preparing multilevel hierarchies..." << std::endl;
178
179 if (multilevel_params.source_enabled)
180 {
181 prepare_source_multilevel();
182 }
183
184 if (multilevel_params.target_enabled)
185 {
186 if (target_points.empty())
187 throw std::runtime_error("Target measure must be set up via setup_target_measure() before preparing hierarchies.");
188
189 prepare_target_multilevel();
190 }
191
192 pcout << "Multilevel hierarchies prepared successfully." << std::endl;
193}
194
195template <int dim, int spacedim>
196Vector<double> SemiDiscreteOT<dim, spacedim>::solve(const Vector<double>& initial_potential)
197{
198 const bool use_multilevel = multilevel_params.source_enabled || multilevel_params.target_enabled;
199
200 Vector<double> final_potentials;
201
202 if (use_multilevel)
203 {
204 final_potentials = run_multilevel(initial_potential);
205 }
206 else
207 {
208 final_potentials = run_sot(initial_potential);
209 }
210
211 pcout << Color::green << Color::bold << "OT solve sequence finished." << Color::reset << std::endl;
212 return final_potentials;
213}
214
215template <int dim, int spacedim>
217{
218 mesh_manager->generate_mesh(source_mesh,
219 source_params.grid_generator_function,
220 source_params.grid_generator_arguments,
221 source_params.n_refinements,
222 source_params.use_tetrahedral_mesh);
223
224 mesh_manager->generate_mesh(target_mesh,
225 target_params.grid_generator_function,
226 target_params.grid_generator_arguments,
227 target_params.n_refinements,
228 target_params.use_tetrahedral_mesh);
229
230 mesh_manager->save_meshes(source_mesh, target_mesh);
231}
232
233template <int dim, int spacedim>
235{
236 mesh_manager->load_source_mesh(source_mesh);
237 mesh_manager->load_target_mesh(target_mesh);
238
239 // Print mesh statistics
240 const unsigned int n_global_cells =
241 Utilities::MPI::sum(source_mesh.n_locally_owned_active_cells(), mpi_communicator);
242 const unsigned int n_global_vertices =
243 Utilities::MPI::sum(source_mesh.n_vertices(), mpi_communicator);
244
245 pcout << "Source mesh: " << n_global_cells << " cells, "
246 << n_global_vertices << " vertices" << std::endl;
247
248 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
250 pcout << "Target mesh: " << target_mesh.n_active_cells() << " cells, "
251 << target_mesh.n_vertices() << " vertices" << std::endl;
252 }
253}
254
255template <int dim, int spacedim>
256std::vector<std::pair<std::string, std::string>> SemiDiscreteOT<dim, spacedim>::get_target_hierarchy_files() const
257{
258 return Utils::get_target_hierarchy_files(multilevel_params.target_hierarchy_dir);
259}
261template <int dim, int spacedim>
263 const std::string& points_file,
264 const std::string& density_file)
265{
266 pcout << "Loading target points from: " << points_file << std::endl;
267 pcout << "Loading target densities from: " << density_file << std::endl;
268
269 std::vector<Point<spacedim>> local_target_points;
270 std::vector<double> local_densities;
271 bool load_success = true;
273 // Only rank 0 reads files
274 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
275 {
276 // Read points
277 if (!Utils::read_vector(local_target_points, points_file, io_coding))
278 {
279 pcout << Color::red << Color::bold << "Error: Cannot read points file: " << points_file << Color::reset << std::endl;
280 load_success = false;
281 }
282
283 // Read potentials
284 if (load_success && !Utils::read_vector(local_densities, density_file, io_coding))
285 {
286 pcout << Color::red << Color::bold << "Error: Cannot read densities file: " << density_file << Color::reset << std::endl;
287 load_success = false;
288 }
290
291 // Broadcast success status
292 load_success = Utilities::MPI::broadcast(mpi_communicator, load_success, 0);
293 if (!load_success)
294 {
295 throw std::runtime_error("Failed to load target points or densities");
297
298 // Broadcast sizes
299 unsigned int n_points = 0;
300 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
301 {
302 n_points = local_target_points.size();
304 n_points = Utilities::MPI::broadcast(mpi_communicator, n_points, 0);
305
306 // Resize containers on non-root ranks
307 if (Utilities::MPI::this_mpi_process(mpi_communicator) != 0)
308 {
309 local_target_points.resize(n_points);
310 local_densities.resize(n_points);
311 }
312
313 // Broadcast data
314 for (unsigned int i = 0; i < n_points; ++i)
315 {
316 local_target_points[i] = Utilities::MPI::broadcast(mpi_communicator, local_target_points[i], 0);
317 local_densities[i] = Utilities::MPI::broadcast(mpi_communicator, local_densities[i], 0);
318 }
319
320 // Update class members
321 target_points = std::move(local_target_points);
322 target_density.reinit(n_points);
323 for (unsigned int i = 0; i < n_points; ++i)
324 {
325 target_density[i] = local_densities[i];
326 }
327
328 pcout << Color::green << "Successfully loaded " << n_points << " target points at this level" << Color::reset << std::endl;
330
331template <int dim, int spacedim>
332void SemiDiscreteOT<dim, spacedim>::load_hierarchy_data(const std::string& hierarchy_dir, int specific_level) {
333 has_hierarchy_data_ = Utils::load_hierarchy_data<dim, spacedim>(hierarchy_dir, child_indices_, specific_level, mpi_communicator, pcout);
334}
335
336template <int dim, int spacedim>
337void SemiDiscreteOT<dim, spacedim>::normalize_density(LinearAlgebra::distributed::Vector<double>& density)
338{
339 auto quadrature = Utils::create_quadrature_for_mesh<dim, spacedim>(source_mesh, solver_params.quadrature_order);
340 // Calculate L1 norm
341 double local_l1_norm = 0.0;
342 FEValues<dim, spacedim> fe_values(*mapping, *fe_system, *quadrature,
343 update_values | update_JxW_values);
344 std::vector<double> density_values(quadrature->size());
345
346 for (const auto &cell : dof_handler_source.active_cell_iterators())
347 {
348 if (!cell->is_locally_owned())
349 continue;
351 fe_values.reinit(cell);
352 fe_values.get_function_values(density, density_values);
353
354 for (unsigned int q = 0; q < quadrature->size(); ++q)
355 {
356 local_l1_norm += std::abs(density_values[q]) * fe_values.JxW(q);
357 }
358 }
359
360 const double global_l1_norm = Utilities::MPI::sum(local_l1_norm, mpi_communicator);
361 pcout << "Density L1 norm before normalization: " << global_l1_norm << std::endl;
362
363 density /= global_l1_norm;
364 density.update_ghost_values();
365}
367
368template <int dim, int spacedim>
370{
371 // Create finite element and mapping for the mesh.
372 auto [fe, map] = Utils::create_fe_and_mapping_for_mesh<dim, spacedim>(source_mesh);
373 fe_system = std::move(fe);
374 mapping = std::move(map);
375
376 // dof_handler_source.clear();
377 dof_handler_source.distribute_dofs(*fe_system);
378
379 IndexSet locally_owned_dofs = dof_handler_source.locally_owned_dofs();
380 IndexSet locally_relevant_dofs;
381 DoFTools::extract_locally_relevant_dofs(dof_handler_source, locally_relevant_dofs);
382
383 source_density.reinit(locally_owned_dofs, locally_relevant_dofs, mpi_communicator);
384
385
386 if (is_multilevel && is_setup_programmatically_ && initial_fine_dof_handler)
388 pcout << Color::green
389 << "Interpolating programmatically provided source density onto current mesh level"
390 << Color::reset << std::endl;
391
392 Utils::interpolate_non_conforming_nearest(*initial_fine_dof_handler,
393 *initial_fine_density,
394 dof_handler_source,
395 source_density);
396
397
398
399 pcout << "Source density interpolated for current multilevel grid." << std::endl;
400 }
401 else
402 {
403 // Handle custom density if enabled.
404 if (source_params.use_custom_density)
405 {
406 if (source_params.density_file_format == "vtk")
407 {
408 if (is_multilevel)
409 {
410 pcout << Color::green << "Interpolating source density from VTK to source mesh" << Color::reset << std::endl;
411
412 // For multilevel, use the stored VTKHandler if available
413 if (source_vtk_handler)
414 {
415 pcout << "Using stored VTKHandler for source density interpolation" << std::endl;
416 VectorTools::interpolate(dof_handler_source, *source_vtk_handler, source_density);
417 }
418 else
419 {
420 // Fallback to non-conforming nearest neighbor interpolation if VTKHandler not available
421 pcout << "VTKHandler not available, using non-conforming nearest neighbor interpolation" << std::endl;
422 Utils::interpolate_non_conforming_nearest(vtk_dof_handler_source,
423 vtk_field_source,
424 dof_handler_source,
425 source_density);
426 }
427
428 pcout << "Source density interpolated from VTK to source mesh" << std::endl;
429 }
430 else
431 {
432 pcout << "Using custom source density from file: " << source_params.density_file_path << std::endl;
433 bool density_loaded = false;
434
435 try
436 {
437 pcout << "Loading VTK file using VTKHandler: " << source_params.density_file_path << std::endl;
438
439 // Create VTKHandler instance and store it as a member variable
440 source_vtk_handler = std::make_unique<VTKHandler<dim,spacedim>>(source_params.density_file_path);
441
442 // Setup the field for interpolation using the configured field name
443 source_vtk_handler->setup_field(source_params.density_field_name, VTKHandler<dim,spacedim>::DataLocation::PointData, 0);
444
445 pcout << "Source density loaded from VTK file" << std::endl;
446 pcout << Color::green << "Interpolating source density from VTK to source mesh" << Color::reset << std::endl;
447
448 // Interpolate the field to the source mesh
449 VectorTools::interpolate(dof_handler_source, *source_vtk_handler, source_density);
450
451 pcout << "Successfully interpolated VTK field to source mesh" << std::endl;
452 density_loaded = true;
453 }
454 catch (const std::exception &e)
455 {
456 pcout << Color::red << "Error loading VTK file: " << e.what() << Color::reset << std::endl;
457 density_loaded = false;
458 }
459
460 if (!density_loaded)
461 {
462 pcout << Color::red << "Failed to load custom density, using uniform density" << Color::reset << std::endl;
463 source_density = 1.0;
464 }
465 }
466 }
467 else
468 {
469 pcout << Color::red << "Unsupported density file format, using uniform density" << Color::reset << std::endl;
470 source_density = 1.0;
471 }
472 }
473 else
474 {
475 pcout << Color::green << "Using uniform source density" << Color::reset << std::endl;
476 source_density = 1.0;
477 }
478 }
479
480 source_density.update_ghost_values();
481 if ( selected_task != "map")
482 {
483 normalize_density(source_density);
484 }
485
486 unsigned int n_locally_owned = 0;
487 for (const auto &cell : dof_handler_source.active_cell_iterators())
488 {
489 if (cell->is_locally_owned())
490 ++n_locally_owned;
491 }
492 const unsigned int n_total_owned = Utilities::MPI::sum(n_locally_owned, mpi_communicator);
493 pcout << "Total cells: " << source_mesh.n_active_cells()
494 << ", Locally owned on proc " << this_mpi_process
495 << ": " << n_locally_owned
496 << ", Sum of owned: " << n_total_owned << std::endl;
497 pcout << "Source mesh finite elements initialized with " << dof_handler_source.n_dofs() << " DoFs" << std::endl;
498}
499
500template <int dim, int spacedim>
502{
503 // Load or compute target points (shared across all processes)
504 const std::string directory = "output/data_points";
505 bool points_loaded = false;
506 target_points.clear(); // Clear on all processes
507
508 // Only root process reads/computes points
509 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
510 auto [fe, map] = Utils::create_fe_and_mapping_for_mesh<dim, spacedim>(target_mesh);
511 fe_system_target = std::move(fe);
512 mapping_target = std::move(map);
513 dof_handler_target.distribute_dofs(*fe_system_target);
514
515 // Load or compute target points
516 if (Utils::read_vector(target_points, directory + "/target_points", io_coding))
517 {
518 points_loaded = true;
519 pcout << "Target points loaded from file" << std::endl;
520 } else {
521 std::map<types::global_dof_index, Point<spacedim>> support_points_target;
522 DoFTools::map_dofs_to_support_points(*mapping_target, dof_handler_target, support_points_target);
523 for (const auto &point_pair : support_points_target)
524 {
525 target_points.push_back(point_pair.second);
526 }
527 Utils::write_vector(target_points, directory + "/target_points", io_coding);
528 points_loaded = true;
529 pcout << "Target points computed and saved to file" << std::endl;
530 }
531
532 if (target_params.use_custom_density)
533 {
534 pcout << "Using custom target density from file: " << target_params.density_file_path << std::endl;
535 bool density_loaded = false;
536
537 if (target_params.density_file_format == "vtk")
538 {
539 // Use the VTKHandler class to load and interpolate the field
540 try
541 {
542 pcout << "Loading VTK file using VTKHandler: " << target_params.density_file_path << std::endl;
543
544 // Create VTKHandler instance
545 VTKHandler<dim,spacedim> vtk_handler(target_params.density_file_path);
546
547 // Setup the field for interpolation using the configured field name
548 vtk_handler.setup_field(target_params.density_field_name, VTKHandler<dim,spacedim>::DataLocation::PointData, 0);
549
550 pcout << "Target density loaded from VTK file" << std::endl;
551 target_density.reinit(dof_handler_target.n_dofs());
552 pcout << "Target density size: " << target_density.size() << std::endl;
553
554 pcout << Color::green << "Interpolating target density from VTK to target mesh" << Color::reset << std::endl;
555
556 // Interpolate the field to the target mesh
557 VectorTools::interpolate(dof_handler_target, vtk_handler, target_density);
558
559 pcout << "Successfully interpolated VTK field to target mesh" << std::endl;
560 pcout << "L1 norm of interpolated field: " << target_density.l1_norm() << std::endl;
561 target_density /= target_density.l1_norm();
562
563 density_loaded = true;
564 }
565 catch (const std::exception &e)
566 {
567 pcout << Color::red << "Error loading VTK file: " << e.what() << Color::reset << std::endl;
568 density_loaded = false;
569 }
570 }
571 else
572 {
573 std::vector<double> density_values;
574 // Try reading as plain text file
575 density_loaded = Utils::read_vector(density_values, target_params.density_file_path);
576 // normalize target density
577 target_density = Vector<double>(density_values.size());
578 for (unsigned int i = 0; i < density_values.size(); ++i)
579 {
580 target_density[i] = density_values[i];
581 }
582 target_density /= target_density.l1_norm();
583 }
584 if (!density_loaded)
585 {
586 pcout << Color::red << "Failed to load custom density, using uniform density" << Color::reset << std::endl;
587 target_density.reinit(target_points.size());
588 target_density = 1.0 / target_points.size();
589 }
590 }
591 else
592 {
593 pcout << Color::green << "Using uniform target density" << Color::reset << std::endl;
594 target_density.reinit(target_points.size());
595 target_density = 1.0 / target_points.size();
596 }
597 }
598
599 // Broadcast success flag
600 points_loaded = Utilities::MPI::broadcast(mpi_communicator, points_loaded, 0);
601 if (!points_loaded)
602 {
603 throw std::runtime_error("Failed to load or compute target points");
604 }
605
606 // Broadcast target points size first
607 unsigned int n_points = 0;
608 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
609 {
610 n_points = target_points.size();
611 }
612 n_points = Utilities::MPI::broadcast(mpi_communicator, n_points, 0);
613
614 // Resize target points and density on non-root processes
615 if (Utilities::MPI::this_mpi_process(mpi_communicator) != 0)
616 {
617 target_points.resize(n_points);
618 target_density.reinit(n_points);
619 }
620
621 target_points = Utilities::MPI::broadcast(mpi_communicator, target_points, 0);
622 target_density = Utilities::MPI::broadcast(mpi_communicator, target_density, 0);
623
624 pcout << "Setup complete with " << target_points.size() << " target points" << std::endl;
625 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
626 {
627 // Create output directory if it doesn't exist
628 std::string output_dir = "output/data_density";
629 fs::create_directories(output_dir);
630
631 // Save density to file
632 std::string density_file = output_dir + "/target_density";
633 std::vector<double> output_density_values(target_density.begin(), target_density.end());
634
635 Utils::write_vector(output_density_values, density_file, io_coding);
636 pcout << "Target density saved to " << density_file << std::endl;
637
638 // Save target points with density as VTK file
639 Utils::write_points_with_density_vtk<spacedim>(
640 target_points,
641 output_density_values,
642 output_dir + "/target_points_with_density.vtk",
643 "Target points with density values",
644 "target_density"
645 );
646 }
647}
648
649
650template <int dim, int spacedim>
652{
653 setup_source_finite_elements();
654 setup_target_finite_elements();
655}
656
657
658template <int dim, int spacedim>
660{
661 mesh_manager->load_target_mesh(target_mesh);
662 setup_target_finite_elements();
663}
664
665template <int dim, int spacedim>
667 Vector<double>& potentials, int coarse_level, int fine_level, const Vector<double>& prev_potentials) {
668
669 if (!has_hierarchy_data_ || coarse_level < 0 || fine_level < 0)
670 {
671 std::cerr << "Invalid hierarchy levels for potential assignment" << std::endl;
672 return;
673 }
674
675 // Direct assignment if same level
676 if (coarse_level == fine_level)
677 {
678 potentials = prev_potentials;
679 return;
680 }
681
682 // Initialize potentials for current level
683 potentials.reinit(target_points.size());
684
685 if (multilevel_params.use_softmax_potential_transfer)
686 {
687 pcout << "Applying softmax-based potential assignment from level " << coarse_level
688 << " to level " << fine_level << std::endl;
689 pcout << "Source points: " << prev_potentials.size()
690 << ", Target points: " << target_points.size() << std::endl;
691
692 // Create SoftmaxRefinement instance
693 SoftmaxRefinement<dim, spacedim> softmax_refiner(
694 mpi_communicator,
695 dof_handler_source,
696 *mapping,
697 *fe_system,
698 source_density,
699 solver_params.quadrature_order,
700 current_distance_threshold,
701 solver_params.use_log_sum_exp_trick);
702
703 // Add timer for softmax refinement
704 Timer softmax_timer;
705 softmax_timer.start();
706
707 // Apply softmax refinement
708 potentials = softmax_refiner.compute_refinement(
709 target_points, // target_points_fine
710 target_density, // target_density_fine
711 target_points_coarse, // target_points_coarse
712 target_density_coarse, // target_density_coarse
713 prev_potentials, // potentials_coarse
714 solver_params.epsilon,
715 fine_level,
716 child_indices_);
717
718 softmax_timer.stop();
719 pcout << "Softmax-based potential assignment completed in " << softmax_timer.wall_time() << " seconds." << std::endl;
720 }
721 else
722 {
723 pcout << "Applying direct potential assignment from level " << coarse_level
724 << " to level " << fine_level << std::endl;
725 pcout << "Source points: " << prev_potentials.size()
726 << ", Target points: " << target_points.size() << std::endl;
727
728 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
729 {
730// Going from coarse to fine: Each child gets its parent's potential
731#pragma omp parallel for
732 for (size_t j = 0; j < prev_potentials.size(); ++j)
733 {
734 const auto &children = child_indices_[fine_level][j];
735 for (size_t child : children)
736 {
737 potentials[child] = prev_potentials[j];
738 }
739 }
740 }
741
742 // Broadcast the potentials to all processes
743 Utilities::MPI::broadcast(mpi_communicator, potentials, 0);
744 }
745}
746
747template <int dim, int spacedim>
748Vector<double> SemiDiscreteOT<dim, spacedim>::run_sot(const Vector<double>& initial_potential)
749{
750 Timer timer;
751 timer.start();
752
753
754 if (!is_setup_programmatically_) {
755 setup_finite_elements();
756 }
757
758
759 pcout << Color::yellow << Color::bold << "Starting SOT optimization with " << target_points.size()
760 << " target points and " << source_density.size() << " source points" << Color::reset << std::endl;
761
762 // Configure solver parameters
763 SotParameterManager::SolverParameters &solver_config = solver_params;
764
765 // Set up source measure
766 sot_solver->setup_source(dof_handler_source,
767 *mapping,
768 *fe_system,
769 source_density,
770 solver_config.quadrature_order);
771
772 // Set up target measure
773 sot_solver->setup_target(target_points, target_density);
774
775 // Initialize potential vector
776 Vector<double> potential;
777 if (initial_potential.size() > 0) {
778 // Verify that the initial potential has the correct size
779 if (initial_potential.size() != target_points.size()) {
780 pcout << Color::red << Color::bold << "Error: Initial potential size (" << initial_potential.size()
781 << ") does not match target points size (" << target_points.size() << ")" << Color::reset << std::endl;
782 return Vector<double>();
783 }
784 pcout << Color::green << "Using provided initial potential" << Color::reset << std::endl;
785 potential = initial_potential;
786 } else {
787 // Initialize with zeros
788 potential.reinit(target_points.size());
789 potential = 0.0;
790 }
791
792 try
793 {
794 if (solver_config.use_epsilon_scaling && epsilon_scaling_handler)
795 {
796 pcout << "Using epsilon scaling with EpsilonScalingHandler:" << std::endl
797 << " Initial epsilon: " << solver_config.epsilon << std::endl
798 << " Scaling factor: " << solver_config.epsilon_scaling_factor << std::endl
799 << " Number of steps: " << solver_config.epsilon_scaling_steps << std::endl;
800 // Compute epsilon distribution for a single level
801 std::vector<std::vector<double>> epsilon_distribution =
802 epsilon_scaling_handler->compute_epsilon_distribution(1);
803
804 if (!epsilon_distribution.empty() && !epsilon_distribution[0].empty())
805 {
806 const auto &epsilon_sequence = epsilon_distribution[0];
807
808 // Run optimization for each epsilon value
809 for (size_t i = 0; i < epsilon_sequence.size(); ++i)
810 {
811 pcout << "\nEpsilon scaling step " << i + 1 << "/" << epsilon_sequence.size()
812 << " (ε = " << epsilon_sequence[i] << ")" << std::endl;
813
814 solver_config.epsilon = epsilon_sequence[i];
815
816 try
817 {
818 sot_solver->solve(potential, solver_config);
819
820 // Save intermediate results
821 if (i < epsilon_sequence.size() - 1)
822 {
823 std::string eps_suffix = "_eps" + std::to_string(i + 1);
824 save_results(potential, "potential" + eps_suffix);
825 }
826 }
827 catch (const SolverControl::NoConvergence &exc)
828 {
829 if (exc.last_step >= solver_params.max_iterations)
830 {
831 pcout << Color::red << Color::bold << " Warning: Optimization failed at step " << i + 1
832 << " (epsilon=" << epsilon_sequence[i] << "): Max iterations reached"
833 << Color::reset << std::endl;
834 }
835 }
836 }
837 }
838 }
839 else
840 {
841 // Run single optimization with original epsilon
842 try
843 {
844 sot_solver->solve(potential, solver_config);
845 }
846 catch (const SolverControl::NoConvergence &exc)
847 {
848 pcout << Color::red << Color::bold << "Warning: Optimization did not converge." << Color::reset << std::endl;
849 }
850 }
851 }
852 catch (const std::exception &e)
853 {
854 pcout << Color::red << Color::bold << "An exception occurred during SOT solve: " << e.what() << Color::reset << std::endl;
855 }
856
857 // Save final results
858 save_results(potential, "potentials");
859
860 timer.stop();
861 const double total_time = timer.wall_time();
862 pcout << "\n"
863 << Color::green << Color::bold << "SOT optimization completed in " << total_time << " seconds" << Color::reset << std::endl;
864
865 // Save convergence info
866 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
867 std::string eps_dir = "output/epsilon_" + Utils::to_scientific_string(solver_config.epsilon);
868 fs::create_directories(eps_dir);
869 std::ofstream conv_info(eps_dir + "/convergence_info.txt");
870 conv_info << "Regularization parameter (ε): " << solver_config.epsilon << "\n";
871 conv_info << "Number of iterations: " << sot_solver->get_last_iteration_count() << "\n";
872 conv_info << "Final function value: " << sot_solver->get_last_functional_value() << "\n";
873 conv_info << "Last threshold value: " << sot_solver->get_last_distance_threshold() << "\n";
874 conv_info << "Total execution time: " << total_time << " seconds\n";
875 conv_info << "Convergence achieved: " << sot_solver->get_convergence_status() << "\n";
876 }
877
878 // Return the computed potentials
879 return potential;
880}
881
882template <int dim, int spacedim>
883Vector<double> SemiDiscreteOT<dim, spacedim>::run_target_multilevel(const Vector<double>& initial_potential)
884{
885 Timer global_timer;
886 global_timer.start();
887
888 // Reset total softmax refinement time
889 double total_softmax_time = 0.0;
890
891 pcout << Color::yellow << Color::bold << "Starting target point cloud multilevel SOT computation..." << Color::reset << std::endl;
892
893 // Load source mesh based on input parameters
894 if (!is_setup_programmatically_)
895 {
896 mesh_manager->load_source_mesh(source_mesh, source_mesh_name);
897 setup_source_finite_elements();
898 }
899
900 // Get target point cloud hierarchy files (sorted from coarsest to finest)
901 unsigned int num_levels = 0;
902 std::vector<std::pair<std::string, std::string>> hierarchy_files;
903 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
904 {
905 try
906 {
907 hierarchy_files = Utils::get_target_hierarchy_files(multilevel_params.target_hierarchy_dir);
908 }
909 catch (const std::exception &e)
910 {
911 pcout << Color::red << Color::bold << "Error: " << e.what() << Color::reset << std::endl;
912 pcout << "Please run prepare_target_multilevel first." << std::endl;
913 return Vector<double>();
914 }
915
916 if (hierarchy_files.empty())
917 {
918 pcout << "No target point cloud hierarchy found. Please run prepare_target_multilevel first." << std::endl;
919 return Vector<double>();
920 }
921 num_levels = hierarchy_files.size();
922 }
923
924 num_levels = Utilities::MPI::broadcast(mpi_communicator, num_levels, 0);
925
926 // Initialize hierarchy data structure but don't load data yet
927 if (!has_hierarchy_data_)
928 {
929 pcout << "Initializing hierarchy data structure..." << std::endl;
930 load_hierarchy_data(multilevel_params.target_hierarchy_dir, -1);
931 }
932
933 if (!has_hierarchy_data_)
934 {
935 pcout << "Failed to initialize hierarchy data. Cannot proceed with multilevel computation." << std::endl;
936 return Vector<double>();
937 }
938
939 // Store original solver parameters
940 const unsigned int original_max_iterations = solver_params.max_iterations;
941 const double original_tolerance = solver_params.tolerance;
942 const double original_regularization = solver_params.epsilon;
943
944 // Create output directory
945 std::string eps_dir = "output/epsilon_" + Utils::to_scientific_string(original_regularization);
946 std::string target_multilevel_dir = eps_dir + "/target_multilevel";
947 fs::create_directories(target_multilevel_dir);
948
949 // Create/Open the summary log file on rank 0
950 std::ofstream summary_log;
951 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
952 summary_log.open(target_multilevel_dir + "/summary_log.txt", std::ios::app);
953 // Write header if file is new or empty
954 if (summary_log.tellp() == 0) {
955 summary_log << "Target Level | Solver Iterations | Time (s) | Last Threshold\n";
956 summary_log << "------------------------------------------------------------\n";
957 }
958 }
959
960 // Setup epsilon scaling if enabled
961 std::vector<std::vector<double>> epsilon_distribution;
962 if (solver_params.use_epsilon_scaling && epsilon_scaling_handler)
963 {
964 pcout << "Computing epsilon distribution for target multilevel optimization..." << std::endl;
965 epsilon_distribution = epsilon_scaling_handler->compute_epsilon_distribution(
966 num_levels);
967 epsilon_scaling_handler->print_epsilon_distribution();
968 }
969
970 // Vector to store current potentials solution
971 Vector<double> level_potentials;
972
973 // Configure solver parameters for this level
974 SotParameterManager::SolverParameters &solver_config = solver_params;
975
976 // Set up source measure (this remains constant across levels)
977 sot_solver->setup_source(dof_handler_source,
978 *mapping,
979 *fe_system,
980 source_density,
981 solver_config.quadrature_order);
982
983 // Process each level of the hierarchy (from coarsest to finest)
984 for (size_t level = 0; level < num_levels; ++level)
985 {
986 int level_number = 0;
987 std::string level_output_dir;
988 std::string points_file;
989 std::string potentials_file;
990 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
991 {
992 const auto &hierarchy_file = hierarchy_files[level];
993 points_file = hierarchy_file.first;
994 potentials_file = hierarchy_file.second;
995
996 // Extract level number from filename
997 std::string level_num = points_file.substr(
998 points_file.find("level_") + 6,
999 points_file.find("_points") - points_file.find("level_") - 6);
1000 level_number = std::stoi(level_num);
1001
1002 // Create output directory for this level
1003 level_output_dir = target_multilevel_dir + "/target_level_" + level_num;
1004 fs::create_directories(level_output_dir);
1005 }
1006 level_number = Utilities::MPI::broadcast(mpi_communicator, level_number, 0);
1007
1008 pcout << "\n"
1009 << Color::magenta << Color::bold << "----------------------------------------" << Color::reset << std::endl;
1010 pcout << Color::magenta << Color::bold << "Processing target point cloud level " << level_number << Color::reset << std::endl;
1011 pcout << Color::magenta << Color::bold << "----------------------------------------" << Color::reset << std::endl;
1012
1013 // Load hierarchy data for this level only
1014 load_hierarchy_data(multilevel_params.target_hierarchy_dir, level_number);
1015
1016 // Load target points for this level
1017 if (level > 0)
1018 {
1019 target_points_coarse = target_points;
1020 target_density_coarse = target_density;
1021 }
1022 load_target_points_at_level(points_file, potentials_file);
1023 pcout << "Target points loaded for level " << level_number << std::endl;
1024 pcout << "Target points size: " << target_points.size() << std::endl;
1025
1026 // Set up target measure for this level
1027 sot_solver->setup_target(target_points, target_density);
1028
1029 // Initialize potentials for this level
1030 Vector<double> current_level_potentials(target_points.size());
1031 if (level > 0)
1032 {
1033 // Use hierarchy-based potential transfer from previous level
1034 Timer transfer_timer;
1035 transfer_timer.start();
1036 assign_potentials_by_hierarchy(current_level_potentials, level_number + 1, level_number, level_potentials);
1037 transfer_timer.stop();
1038
1039 // Update total softmax time if using softmax transfer
1040 if (multilevel_params.use_softmax_potential_transfer)
1041 {
1042 total_softmax_time += transfer_timer.wall_time();
1043 }
1044 }
1045 else
1046 {
1047 // For the coarsest level (level == 0), use the provided initial potential
1048 if (initial_potential.size() > 0)
1049 {
1050 if (initial_potential.size() != target_points.size())
1051 {
1052 pcout << Color::red << Color::bold << "Error: Initial potential size (" << initial_potential.size()
1053 << ") does not match coarsest target points size (" << target_points.size() << ")" << Color::reset << std::endl;
1054 return Vector<double>();
1055 }
1056 pcout << Color::green << "Using provided initial potential for coarsest level " << level_number << Color::reset << std::endl;
1057 current_level_potentials = initial_potential;
1058 }
1059 else
1060 {
1061 // Initialize with zeros for coarsest level
1062 current_level_potentials = 0.0;
1063 }
1064 }
1065
1066 Timer level_timer;
1067 level_timer.start();
1068
1069 // Apply epsilon scaling for this level if enabled
1070 if (solver_params.use_epsilon_scaling && epsilon_scaling_handler && !epsilon_distribution.empty())
1071 {
1072 const auto &level_epsilons = epsilon_scaling_handler->get_epsilon_values_for_level(level);
1073
1074 if (!level_epsilons.empty())
1075 {
1076 pcout << "Using " << level_epsilons.size() << " epsilon values for level " << level_number << std::endl;
1077
1078 // Process each epsilon value for this level
1079 for (size_t eps_idx = 0; eps_idx < level_epsilons.size(); ++eps_idx)
1080 {
1081 double current_epsilon = level_epsilons[eps_idx];
1082 pcout << " Epsilon scaling step " << eps_idx + 1 << "/" << level_epsilons.size()
1083 << " (ε = " << current_epsilon << ")" << std::endl;
1084
1085 // Update regularization parameter
1086 solver_config.epsilon = current_epsilon;
1087
1088 try
1089 {
1090 // Run optimization with current epsilon
1091 sot_solver->solve(current_level_potentials, solver_config);
1092
1093 // Save intermediate results if this is not the last epsilon for this level
1094 if (eps_idx < level_epsilons.size() - 1)
1095 {
1096 std::string eps_suffix = "_eps" + std::to_string(eps_idx + 1);
1097 save_results(current_level_potentials, level_output_dir + "/potentials" + eps_suffix, false);
1098 }
1099 }
1100 catch (const SolverControl::NoConvergence &exc)
1101 {
1102 if (exc.last_step >= solver_params.max_iterations)
1103 {
1104 pcout << Color::red << Color::bold << " Warning: Optimization failed at step " << eps_idx + 1
1105 << " (epsilon=" << current_epsilon << "): Max iterations reached"
1106 << Color::reset << std::endl;
1107 }
1108 pcout << Color::red << Color::bold << " Warning: Optimization did not converge for epsilon "
1109 << current_epsilon << " at target level " << level_number << Color::reset << std::endl;
1110 // Continue with next epsilon value
1111 }
1112 }
1113 }
1114 else
1115 {
1116 // If no epsilon values for this level, use the smallest epsilon from the sequence
1117 solver_config.epsilon = original_regularization;
1118
1119 try
1120 {
1121 // Run optimization with default epsilon
1122 sot_solver->solve(current_level_potentials, solver_config);
1123 }
1124 catch (const SolverControl::NoConvergence &exc)
1125 {
1126 if (exc.last_step >= solver_params.max_iterations)
1127 {
1128 pcout << Color::red << Color::bold << " Warning: Optimization failed at step " << level_number
1129 << " (epsilon=" << original_regularization << "): Max iterations reached"
1130 << Color::reset << std::endl;
1131 }
1132 pcout << Color::red << Color::bold << "Warning: Optimization did not converge for level " << level_number << Color::reset << std::endl;
1133 pcout << " Iterations: " << exc.last_step << std::endl;
1134 }
1135 }
1136 }
1137 else
1138 {
1139 // No epsilon scaling, just run the optimization once
1140 try
1141 {
1142 // Run optimization for this level
1143 sot_solver->solve(current_level_potentials, solver_config);
1144 }
1145 catch (SolverControl::NoConvergence &exc)
1146 {
1147 if (exc.last_step >= solver_params.max_iterations)
1148 {
1149 pcout << Color::red << Color::bold << " Warning: Optimization failed at step " << level_number
1150 << " (epsilon=" << original_regularization << "): Max iterations reached"
1151 << Color::reset << std::endl;
1152 }
1153 pcout << Color::red << Color::bold << "Warning: Optimization did not converge for level " << level_number << Color::reset << std::endl;
1154 pcout << " Iterations: " << exc.last_step << std::endl;
1155 if (level == 0)
1156 return Vector<double>();
1157 }
1158 }
1159
1160 level_timer.stop();
1161 const double level_time = level_timer.wall_time();
1162 const unsigned int last_iterations = sot_solver->get_last_iteration_count();
1163 current_distance_threshold = sot_solver->get_last_distance_threshold();
1164
1165 // Log summary information to file (only rank 0)
1166 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0 && summary_log.is_open()) {
1167 summary_log << std::setw(13) << level_number << " | "
1168 << std::setw(17) << last_iterations << " | "
1169 << std::setw(8) << std::fixed << std::setprecision(4) << level_time << " | "
1170 << std::setw(14) << std::scientific << std::setprecision(6) << current_distance_threshold << "\n";
1171 }
1172
1173 // Save results for this level
1174 save_results(current_level_potentials, level_output_dir + "/potentials", false);
1175 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
1176 {
1177 std::ofstream conv_info(level_output_dir + "/convergence_info.txt");
1178 conv_info << "Regularization parameter (ε): " << solver_params.epsilon << "\n";
1179 conv_info << "Number of iterations: " << sot_solver->get_last_iteration_count() << "\n";
1180 conv_info << "Final function value: " << sot_solver->get_last_functional_value() << "\n";
1181 conv_info << "Convergence achieved: " << sot_solver->get_convergence_status() << "\n";
1182 conv_info << "Level: " << level_number << "\n";
1183 pcout << "Level " << level_number << " results saved to " << level_output_dir << "/potentials" << std::endl;
1184 }
1185
1186 // Store potentials for next level
1187 level_potentials = current_level_potentials;
1188
1189 // Store coarsest potential for the first level (level == 0)
1190 if (level == 0)
1191 {
1192 coarsest_potential = current_level_potentials;
1193 }
1194 }
1195
1196 // Save final potentials in the top-level directory
1197 save_results(level_potentials, target_multilevel_dir + "/potentials", false);
1198
1199 // Close the log file on rank 0
1200 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0 && summary_log.is_open()) {
1201 summary_log.close();
1202 }
1203
1204 // Restore original parameters
1205 solver_params.tolerance = original_tolerance;
1206 solver_params.max_iterations = original_max_iterations;
1207 solver_params.epsilon = original_regularization;
1208
1209 global_timer.stop();
1210 const double total_time = global_timer.wall_time();
1211
1212 // Save global convergence info
1213 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
1214 std::ofstream conv_info(target_multilevel_dir + "/convergence_info.txt");
1215 conv_info << "Regularization parameter (ε): " << original_regularization << "\n";
1216 conv_info << "Final number of iterations: " << sot_solver->get_last_iteration_count() << "\n";
1217 conv_info << "Final function value: " << sot_solver->get_last_functional_value() << "\n";
1218 conv_info << "Last threshold value: " << current_distance_threshold << "\n";
1219 conv_info << "Total execution time: " << total_time << " seconds\n";
1220 conv_info << "Convergence achieved: " << sot_solver->get_convergence_status() << "\n";
1221 conv_info << "Number of levels: " << num_levels << "\n";
1222 }
1223
1224 pcout << "\n"
1225 << Color::magenta << Color::bold << "----------------------------------------" << Color::reset << std::endl;
1226 pcout << Color::magenta << Color::bold << "Total multilevel target computation time: " << total_time << " seconds" << Color::reset << std::endl;
1227
1228 // Report total softmax time if applicable
1229 if (multilevel_params.use_softmax_potential_transfer && total_softmax_time > 0.0)
1230 {
1231 pcout << Color::magenta << Color::bold << "Total time spent on softmax potential transfers: " << total_softmax_time
1232 << " seconds (" << (total_softmax_time / total_time * 100.0) << "%)" << Color::reset << std::endl;
1233 }
1234
1235 pcout << Color::magenta << Color::bold << "----------------------------------------" << Color::reset << std::endl;
1236
1237 // Return the final potentials from the finest level
1238 return level_potentials;
1239}
1240
1241template <int dim, int spacedim>
1242Vector<double> SemiDiscreteOT<dim, spacedim>::run_source_multilevel(const Vector<double>& initial_potential)
1243{
1244 Timer global_timer;
1245 global_timer.start();
1246
1247 pcout << Color::yellow << Color::bold << "Starting source multilevel SOT computation..." << Color::reset << std::endl;
1248
1249 // Retrieve source mesh hierarchy files.
1250 std::vector<std::string> source_mesh_files = mesh_manager->get_mesh_hierarchy_files(multilevel_params.source_hierarchy_dir);
1251 if (source_mesh_files.empty())
1252 {
1253 pcout << "No source mesh hierarchy found. Please run prepare_source_multilevel first." << std::endl;
1254 return Vector<double>();
1255 }
1256 unsigned int num_levels = source_mesh_files.size();
1257
1258 // Backup original solver parameters.
1259 const unsigned int original_max_iterations = solver_params.max_iterations;
1260 const double original_tolerance = solver_params.tolerance;
1261 const double original_regularization = solver_params.epsilon;
1262
1263 // Create output directory structure.
1264 std::string eps_dir = "output/epsilon_" + Utils::to_scientific_string(original_regularization);
1265 std::string source_multilevel_dir = eps_dir + "/source_multilevel";
1266 fs::create_directories(source_multilevel_dir);
1267
1268 // Create/Open the summary log file on rank 0
1269 std::ofstream summary_log;
1270 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
1271 summary_log.open(source_multilevel_dir + "/summary_log.txt", std::ios::app);
1272 // Write header if file is new or empty
1273 if (summary_log.tellp() == 0) {
1274 summary_log << "Source Level | Solver Iterations | Time (s) | Last Threshold\n";
1275 summary_log << "------------------------------------------------------------\n";
1276 }
1277 }
1278
1279 // Setup epsilon scaling if enabled.
1280 std::vector<std::vector<double>> epsilon_distribution;
1281 if (solver_params.use_epsilon_scaling && epsilon_scaling_handler)
1282 {
1283 pcout << "Computing epsilon distribution for source multilevel optimization..." << std::endl;
1284 epsilon_distribution = epsilon_scaling_handler->compute_epsilon_distribution(num_levels);
1285 epsilon_scaling_handler->print_epsilon_distribution();
1286 }
1287
1288 // Lambda to encapsulate the epsilon scaling and solver call.
1289 auto process_epsilon_scaling_for_source_multilevel =
1290 [this, &original_regularization, &epsilon_distribution](
1291 Vector<double> &potentials,
1292 const unsigned int level_idx, // 0 to num_levels-1
1293 const std::string &level_output_dir) {
1294 if (solver_params.use_epsilon_scaling && epsilon_scaling_handler && !epsilon_distribution.empty())
1295 {
1296 const auto &level_epsilons = epsilon_scaling_handler->get_epsilon_values_for_level(level_idx);
1297 if (!level_epsilons.empty())
1298 {
1299 pcout << "Using " << level_epsilons.size() << " epsilon values for source level " << level_idx << std::endl;
1300 for (size_t eps_idx = 0; eps_idx < level_epsilons.size(); ++eps_idx)
1301 {
1302 double current_epsilon = level_epsilons[eps_idx];
1303 pcout << " Epsilon scaling step " << eps_idx + 1 << "/" << level_epsilons.size()
1304 << " (ε = " << current_epsilon << ")" << std::endl;
1305 solver_params.epsilon = current_epsilon;
1306
1307 try
1308 {
1309 sot_solver->solve(potentials, solver_params);
1310 if (eps_idx < level_epsilons.size() - 1)
1311 {
1312 std::string eps_suffix = "_eps" + std::to_string(eps_idx + 1);
1313 save_results(potentials, level_output_dir + "/potentials" + eps_suffix, false);
1314 }
1315 }
1316 catch (const SolverControl::NoConvergence &exc)
1317 {
1318 if (exc.last_step >= solver_params.max_iterations)
1319 {
1320 pcout << Color::red << Color::bold << " Warning: Optimization failed at step " << eps_idx + 1
1321 << " (epsilon=" << current_epsilon << "): Max iterations reached" << Color::reset << std::endl;
1322 }
1323 pcout << Color::red << Color::bold << " Warning: Optimization did not converge for epsilon "
1324 << current_epsilon << " at source level " << level_idx << Color::reset << std::endl;
1325 // Continue with the next epsilon value.
1326 }
1327 }
1328 return; // Epsilon scaling applied
1329 }
1330 }
1331 // If no epsilon scaling is applied or no epsilon values exist for this level.
1332 solver_params.epsilon = original_regularization;
1333 sot_solver->solve(potentials, solver_params);
1334 };
1335
1336 Vector<double> final_potentials;
1337 Vector<double> previous_source_potentials;
1338
1339 if (!is_setup_programmatically_) {
1340 mesh_manager->load_source_mesh(source_mesh, source_mesh_name);
1341 setup_source_finite_elements();
1342 setup_target_points();
1343 }
1344
1345 for (size_t source_level_idx = 0; source_level_idx < source_mesh_files.size(); ++source_level_idx)
1346 {
1347 const unsigned int current_level_display_name = num_levels - source_level_idx -1 ;
1348
1349 pcout << Color::cyan << Color::bold
1350 << "============================================" << Color::reset << std::endl;
1351 pcout << Color::cyan << Color::bold << "Processing source mesh level " << current_level_display_name
1352 << " (mesh: " << source_mesh_files[source_level_idx] << ")" << Color::reset << std::endl;
1353 pcout << Color::cyan << Color::bold
1354 << "============================================" << Color::reset << std::endl;
1355
1356 std::string source_level_dir = source_multilevel_dir + "/source_level_" + std::to_string(current_level_display_name);
1357 fs::create_directories(source_level_dir);
1358
1359 Vector<double> level_potentials;
1360 Timer level_timer;
1361 level_timer.start();
1362
1363 mesh_manager->load_mesh_at_level(source_mesh, dof_handler_source, source_mesh_files[source_level_idx]);
1364 setup_source_finite_elements(true); // true for is_multilevel
1365
1366 if (source_level_idx == 0)
1367 {
1368 level_potentials.reinit(target_points.size());
1369 // For the coarsest level, use the provided initial potential
1370 if (initial_potential.size() > 0)
1371 {
1372 if (initial_potential.size() != target_points.size())
1373 {
1374 pcout << Color::red << Color::bold << "Error: Initial potential size (" << initial_potential.size()
1375 << ") does not match target points size (" << target_points.size() << ")" << Color::reset << std::endl;
1376 return Vector<double>();
1377 }
1378 pcout << Color::green << "Using provided initial potential for coarsest source level " << current_level_display_name << Color::reset << std::endl;
1379 level_potentials = initial_potential;
1380 }
1381 else
1382 {
1383 level_potentials = 0.0; // Initialize potentials for the coarsest level
1384 }
1385 }
1386 else
1387 {
1388 level_potentials.reinit(previous_source_potentials.size());
1389 level_potentials = previous_source_potentials;
1390 }
1391 pcout << " Max iterations: " << solver_params.max_iterations << std::endl;
1392
1393
1394 sot_solver->setup_source(dof_handler_source, *mapping, *fe_system, source_density, solver_params.quadrature_order);
1395 sot_solver->setup_target(target_points, target_density);
1396
1397 process_epsilon_scaling_for_source_multilevel(level_potentials, source_level_idx, source_level_dir);
1398
1399 level_timer.stop();
1400 const double level_time = level_timer.wall_time();
1401 const unsigned int last_iterations = sot_solver->get_last_iteration_count();
1402 const double last_threshold = sot_solver->get_last_distance_threshold();
1403
1404 // Log summary information to file (only rank 0)
1405 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0 && summary_log.is_open()) {
1406 summary_log << std::setw(13) << current_level_display_name << " | "
1407 << std::setw(17) << last_iterations << " | "
1408 << std::setw(8) << std::fixed << std::setprecision(4) << level_time << " | "
1409 << std::setw(14) << std::scientific << std::setprecision(6) << last_threshold << "\n";
1410 }
1411
1412 save_results(level_potentials, source_level_dir + "/potentials", false);
1413
1414 pcout << Color::blue << Color::bold << "Source level " << current_level_display_name << " summary:" << Color::reset << std::endl;
1415 pcout << Color::blue << " Status: Completed" << Color::reset << std::endl;
1416 pcout << Color::blue << " Time taken: " << level_timer.wall_time() << " seconds" << Color::reset << std::endl;
1417 pcout << Color::blue << " Final number of iterations: " << sot_solver->get_last_iteration_count() << Color::reset << std::endl;
1418 pcout << Color::blue << " Final function value: " << sot_solver->get_last_functional_value() << Color::reset << std::endl;
1419 pcout << Color::blue << " Results saved in: " << source_level_dir << Color::reset << std::endl;
1420
1421 previous_source_potentials = level_potentials;
1422 if (source_level_idx == source_mesh_files.size() - 1)
1423 {
1424 final_potentials = level_potentials;
1425 // Store final potential for source multilevel (finest source level)
1426 coarsest_potential = level_potentials;
1427 }
1428 }
1429
1430 // Save final results from the finest level
1431 save_results(final_potentials, source_multilevel_dir + "/potentials", false);
1432
1433 // Close the log file on rank 0
1434 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0 && summary_log.is_open()) {
1435 summary_log.close();
1436 }
1437
1438 solver_params.max_iterations = original_max_iterations;
1439 solver_params.tolerance = original_tolerance;
1440 solver_params.epsilon = original_regularization;
1441
1442 global_timer.stop();
1443 const double total_time = global_timer.wall_time();
1444
1445 // Save global convergence info
1446 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
1447 std::ofstream conv_info(source_multilevel_dir + "/convergence_info.txt");
1448 conv_info << "Regularization parameter (ε): " << original_regularization << "\n";
1449 conv_info << "Final number of iterations: " << sot_solver->get_last_iteration_count() << "\n";
1450 conv_info << "Final function value: " << sot_solver->get_last_functional_value() << "\n";
1451 conv_info << "Last threshold value: " << sot_solver->get_last_distance_threshold() << "\n";
1452 conv_info << "Total execution time: " << total_time << " seconds\n";
1453 conv_info << "Convergence achieved: " << sot_solver->get_convergence_status() << "\n";
1454 conv_info << "Number of levels: " << num_levels << "\n";
1455 }
1456
1457 pcout << "\n"
1459 << "============================================" << Color::reset << std::endl;
1460 pcout << Color::green << Color::bold << "Source multilevel SOT computation completed!" << Color::reset << std::endl;
1461 pcout << Color::green << Color::bold << "Total computation time: " << total_time << " seconds" << Color::reset << std::endl;
1462 pcout << Color::green << "Final results saved in: " << source_multilevel_dir << Color::reset << std::endl;
1463 pcout << Color::green << Color::bold
1464 << "============================================" << Color::reset << std::endl;
1465
1466 // Return the final potentials from the finest level
1467 return final_potentials;
1468}
1469
1470// TODO check if there are bug with dim, spacedim, custom distance
1471template <int dim, int spacedim>
1472Vector<double> SemiDiscreteOT<dim, spacedim>::run_combined_multilevel(const Vector<double>& initial_potential)
1473{
1474 Timer global_timer;
1475 global_timer.start();
1476
1477 pcout << Color::yellow << Color::bold << "Starting combined multilevel SOT computation..." << Color::reset << std::endl;
1478
1479 // Get source mesh hierarchy files
1480 std::vector<std::string> source_mesh_files = mesh_manager->get_mesh_hierarchy_files(multilevel_params.source_hierarchy_dir);
1481 unsigned int n_s_levels = Utilities::MPI::broadcast(mpi_communicator, source_mesh_files.size(), 0);
1482
1483 // Get target point cloud hierarchy files
1484 std::vector<std::pair<std::string, std::string>> target_hierarchy_files;
1485 unsigned int n_t_levels = 0;
1486 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
1487 target_hierarchy_files = Utils::get_target_hierarchy_files(multilevel_params.target_hierarchy_dir);
1488 n_t_levels = target_hierarchy_files.size();
1489 }
1490 n_t_levels = Utilities::MPI::broadcast(mpi_communicator, n_t_levels, 0);
1491
1492 // Calculate total combined levels
1493 const unsigned int n_c_levels = std::max(n_s_levels, n_t_levels);
1494
1495 // Backup original solver parameters
1496 const unsigned int original_max_iterations = solver_params.max_iterations;
1497 const double original_tolerance = solver_params.tolerance;
1498 const double original_regularization = solver_params.epsilon;
1499
1500 // Create output directory
1501 std::string eps_dir = "output/epsilon_" + Utils::to_scientific_string(original_regularization);
1502 std::string combined_multilevel_dir = eps_dir + "/combined_multilevel";
1503 fs::create_directories(combined_multilevel_dir);
1504
1505 // Create/Open the summary log file on rank 0
1506 std::ofstream summary_log;
1507 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
1508 summary_log.open(combined_multilevel_dir + "/summary_log.txt", std::ios::app);
1509 // Write header if file is new or empty
1510 if (summary_log.tellp() == 0) {
1511 summary_log << "Combined Level | Solver Iterations | Time (s) | Last Threshold\n";
1512 summary_log << "------------------------------------------------------------\n";
1513 }
1514 }
1515
1516 // Setup epsilon scaling if enabled
1517 if (solver_params.use_epsilon_scaling && epsilon_scaling_handler) {
1518 pcout << "Computing epsilon distribution for combined multilevel optimization..." << std::endl;
1519 epsilon_scaling_handler->compute_epsilon_distribution(n_c_levels);
1520 epsilon_scaling_handler->print_epsilon_distribution();
1521 }
1522
1523 // Initialize state variables
1524 Vector<double> current_potentials;
1525 int prev_actual_target_level_num = -1;
1526 unsigned int prev_source_idx = -1; // Track previous source index
1527 double total_softmax_time = 0.0;
1528
1529 // loading mesh and setup FEs, needed for first iteration in order to load interpolate
1530 if (!is_setup_programmatically_) {
1531 mesh_manager->load_source_mesh(source_mesh);
1532 setup_source_finite_elements();
1533 }
1534
1535 // Main Loop (Combined Levels)
1536 for (unsigned int combined_iter = 0; combined_iter < n_c_levels; ++combined_iter) {
1537 Timer level_timer;
1538 level_timer.start();
1539
1540 pcout << "\n" << Color::cyan << Color::bold
1541 << "============================================" << Color::reset << std::endl;
1542 pcout << Color::cyan << Color::bold << "Processing combined level " << combined_iter
1543 << " of " << n_c_levels - 1 << Color::reset << std::endl;
1544
1545 // Determine Source and Target indices
1546 unsigned int current_source_idx = 0;
1547 unsigned int current_target_idx = 0;
1548
1549 if (n_s_levels >= n_t_levels) {
1550 current_source_idx = combined_iter;
1551 current_target_idx = (combined_iter >= (n_s_levels - n_t_levels)) ?
1552 (combined_iter - (n_s_levels - n_t_levels)) : 0;
1553 } else {
1554 current_target_idx = combined_iter;
1555 current_source_idx = (combined_iter >= (n_t_levels - n_s_levels)) ?
1556 (combined_iter - (n_t_levels - n_s_levels)) : 0;
1557 }
1558
1559 // Get current files
1560 const std::string current_source_mesh_file = source_mesh_files[current_source_idx];
1561 std::string current_target_points_file;
1562 std::string current_target_density_file;
1563
1564 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
1565 current_target_points_file = target_hierarchy_files[current_target_idx].first;
1566 current_target_density_file = target_hierarchy_files[current_target_idx].second;
1567 }
1568
1569 // Broadcast filenames
1570 current_target_points_file = Utilities::MPI::broadcast(mpi_communicator, current_target_points_file, 0);
1571 current_target_density_file = Utilities::MPI::broadcast(mpi_communicator, current_target_density_file, 0);
1572
1573 // Extract target level number
1574 int current_actual_target_level_num = -1;
1575 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
1576 size_t level_pos = current_target_points_file.find("level_");
1577 size_t level_end = current_target_points_file.find("_points");
1578 if (level_pos != std::string::npos && level_end != std::string::npos) {
1579 current_actual_target_level_num = std::stoi(current_target_points_file.substr(
1580 level_pos + 6, level_end - (level_pos + 6)));
1581 }
1582 }
1583 current_actual_target_level_num = Utilities::MPI::broadcast(mpi_communicator, current_actual_target_level_num, 0);
1584
1585 // Create level directory
1586 std::string level_dir = combined_multilevel_dir + "/combined_level_" + std::to_string(combined_iter);
1587 fs::create_directories(level_dir);
1588
1589 // Only load source mesh and setup FEs if source level changed
1590 if (current_source_idx != prev_source_idx) {
1591 mesh_manager->load_mesh_at_level(source_mesh, dof_handler_source, current_source_mesh_file);
1592 setup_source_finite_elements(true);
1593 prev_source_idx = current_source_idx;
1594 }
1595
1596 // Save previous target data if not first iteration
1597 if (combined_iter > 0) {
1598 target_points_coarse = target_points;
1599 target_density_coarse = target_density;
1600 }
1601
1602 // Load target points
1603 load_target_points_at_level(current_target_points_file, current_target_density_file);
1604
1605 // Initialize potentials
1606 Vector<double> potentials_for_this_level(target_points.size());
1607
1608 if (combined_iter == 0) {
1609 // For the first iteration (coarsest level), use the provided initial potential
1610 if (initial_potential.size() > 0) {
1611 if (initial_potential.size() != target_points.size()) {
1612 pcout << Color::red << Color::bold << "Error: Initial potential size (" << initial_potential.size()
1613 << ") does not match coarsest target points size (" << target_points.size() << ")" << Color::reset << std::endl;
1614 return Vector<double>();
1615 }
1616 pcout << Color::green << "Using provided initial potential for coarsest combined level " << combined_iter << Color::reset << std::endl;
1617 potentials_for_this_level = initial_potential;
1618 } else {
1619 potentials_for_this_level = 0.0; // Default initialization
1620 }
1621 } else {
1622 bool target_level_changed = (current_actual_target_level_num != prev_actual_target_level_num);
1623 bool hierarchical_transfer_possible = target_level_changed &&
1624 (prev_actual_target_level_num == current_actual_target_level_num + 1);
1625
1626 if (hierarchical_transfer_possible) {
1627 load_hierarchy_data(multilevel_params.target_hierarchy_dir, current_actual_target_level_num);
1628 Timer transfer_timer;
1629 transfer_timer.start();
1630
1631 assign_potentials_by_hierarchy(potentials_for_this_level,
1632 prev_actual_target_level_num,
1633 current_actual_target_level_num,
1634 current_potentials);
1635
1636 transfer_timer.stop();
1637 if (multilevel_params.use_softmax_potential_transfer) {
1638 total_softmax_time += transfer_timer.wall_time();
1639 }
1640 } else if (current_potentials.size() == potentials_for_this_level.size()) {
1641 potentials_for_this_level = current_potentials;
1642 }
1643 }
1644
1645 // Setup solver
1646 sot_solver->setup_source(dof_handler_source, *mapping, *fe_system, source_density, solver_params.quadrature_order);
1647 sot_solver->setup_target(target_points, target_density);
1648
1649 // Solve with epsilon scaling
1650 bool solve_successful = true;
1651 if (solver_params.use_epsilon_scaling && epsilon_scaling_handler) {
1652 const auto& level_epsilons = epsilon_scaling_handler->get_epsilon_values_for_level(combined_iter);
1653 if (!level_epsilons.empty()) {
1654 for (size_t eps_idx = 0; eps_idx < level_epsilons.size(); ++eps_idx) {
1655 solver_params.epsilon = level_epsilons[eps_idx];
1656 try {
1657 sot_solver->solve(potentials_for_this_level, solver_params);
1658 if (eps_idx < level_epsilons.size() - 1) {
1659 save_results(potentials_for_this_level, level_dir + "/potentials_eps" + std::to_string(eps_idx + 1), false);
1660 }
1661 } catch (const SolverControl::NoConvergence& exc) {
1662 solve_successful = false;
1663 }
1664 }
1665 } else {
1666 solver_params.epsilon = original_regularization;
1667 try {
1668 sot_solver->solve(potentials_for_this_level, solver_params);
1669 } catch (const SolverControl::NoConvergence& exc) {
1670 solve_successful = false;
1671 }
1672 }
1673 } else {
1674 solver_params.epsilon = original_regularization;
1675 try {
1676 sot_solver->solve(potentials_for_this_level, solver_params);
1677 } catch (const SolverControl::NoConvergence& exc) {
1678 solve_successful = false;
1679 }
1680 }
1681
1682 if (!solve_successful && combined_iter == 0) {
1683 pcout << Color::red << Color::bold << "Initial iteration failed. Aborting." << Color::reset << std::endl;
1684 break;
1685 }
1686
1687 // Save results
1688 save_results(potentials_for_this_level, level_dir + "/potentials", false);
1689
1690 // Update state for next iteration
1691 current_potentials = potentials_for_this_level;
1692 prev_actual_target_level_num = current_actual_target_level_num;
1693 current_distance_threshold = sot_solver->get_last_distance_threshold();
1694
1695 // Store coarsest potential for the first iteration (combined_iter == 0)
1696 if (combined_iter == 0)
1697 {
1698 coarsest_potential = potentials_for_this_level;
1699 }
1700
1701 level_timer.stop();
1702 const double level_time = level_timer.wall_time();
1703 const unsigned int last_iterations = sot_solver->get_last_iteration_count();
1704
1705 // Log summary information to file (only rank 0)
1706 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0 && summary_log.is_open()) {
1707 summary_log << std::setw(13) << combined_iter << " | "
1708 << std::setw(17) << last_iterations << " | "
1709 << std::setw(8) << std::fixed << std::setprecision(4) << level_time << " | "
1710 << std::setw(14) << std::scientific << std::setprecision(6) << current_distance_threshold << "\n";
1711 }
1712 }
1713
1714 // Save final potentials in the top-level directory
1715 if (current_potentials.size() > 0) {
1716 save_results(current_potentials, combined_multilevel_dir + "/potentials", false);
1717 }
1718
1719 // Close the log file on rank 0
1720 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0 && summary_log.is_open()) {
1721 summary_log.close();
1722 }
1723
1724 // Restore original parameters
1725 solver_params.max_iterations = original_max_iterations;
1726 solver_params.tolerance = original_tolerance;
1727 solver_params.epsilon = original_regularization;
1728
1729 global_timer.stop();
1730 const double total_time = global_timer.wall_time();
1731
1732 // Save global convergence info
1733 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0) {
1734 std::ofstream conv_info(combined_multilevel_dir + "/convergence_info.txt");
1735 conv_info << "Regularization parameter (ε): " << original_regularization << "\n";
1736 conv_info << "Final number of iterations: " << sot_solver->get_last_iteration_count() << "\n";
1737 conv_info << "Final function value: " << sot_solver->get_last_functional_value() << "\n";
1738 conv_info << "Last threshold value: " << current_distance_threshold << "\n";
1739 conv_info << "Total execution time: " << total_time << " seconds\n";
1740 conv_info << "Convergence achieved: " << sot_solver->get_convergence_status() << "\n";
1741 conv_info << "Number of levels: " << n_c_levels << "\n";
1742 }
1743
1744 pcout << "\n" << Color::green << Color::bold
1745 << "============================================" << Color::reset << std::endl;
1746 pcout << Color::green << Color::bold << "Combined multilevel computation completed!" << Color::reset << std::endl;
1747 pcout << Color::green << Color::bold << "Total computation time: " << total_time << " seconds" << Color::reset << std::endl;
1748 if (multilevel_params.use_softmax_potential_transfer && total_softmax_time > 0.0) {
1749 pcout << Color::green << Color::bold << "Total softmax transfer time: " << total_softmax_time
1750 << " seconds (" << (total_softmax_time / total_time * 100.0) << "%)" << Color::reset << std::endl;
1751 }
1752 pcout << Color::green << Color::bold << "============================================" << Color::reset << std::endl;
1753
1754 // Return the final potentials from the finest combined level
1755 return current_potentials;
1756}
1757
1758template <int dim, int spacedim>
1759Vector<double> SemiDiscreteOT<dim, spacedim>::run_multilevel(const Vector<double>& initial_potential)
1760{
1761 pcout << Color::yellow << Color::bold << "Starting multilevel SOT computation (dispatcher)..." << Color::reset << std::endl;
1762
1763 const bool source_ml_enabled = multilevel_params.source_enabled;
1764 const bool target_ml_enabled = multilevel_params.target_enabled;
1765
1766 if (source_ml_enabled && !target_ml_enabled)
1767 {
1768 pcout << "Executing Source-Only Multilevel SOT." << std::endl;
1769 return run_source_multilevel(initial_potential);
1770 }
1771 else if (!source_ml_enabled && target_ml_enabled)
1772 {
1773 pcout << "Executing Target-Only Multilevel SOT." << std::endl;
1774 return run_target_multilevel(initial_potential);
1775 }
1776 else if (source_ml_enabled && target_ml_enabled)
1777 {
1778 pcout << "Executing Combined Source and Target Multilevel SOT." << std::endl;
1779 return run_combined_multilevel(initial_potential);
1780 }
1781 else
1782 {
1783 pcout << Color::red << Color::bold
1784 << "Error: No multilevel strategy enabled (neither source nor target). "
1785 << "Please enable at least one in parameters.prm and select the 'multilevel_sot' task."
1786 << Color::reset << std::endl;
1787
1788 // Return empty vector on error
1789 return Vector<double>();
1790 }
1791}
1792
1793template <int dim, int spacedim>
1794template <int d, int s>
1795typename std::enable_if<d == 3 && s == 3>::type SemiDiscreteOT<dim, spacedim>::run_exact_sot()
1796{
1797 pcout << "Running exact SOT computation..." << std::endl;
1798
1799 ExactSot exact_solver;
1800
1801 // Set source mesh and target points
1802 if (!exact_solver.set_source_mesh("output/data_mesh/source.msh"))
1803 {
1804 pcout << "Failed to load source mesh for exact SOT" << std::endl;
1805 return;
1806 }
1807
1808 // Load target points from the same location as used in setup_finite_elements
1809 const std::string directory = "output/data_points";
1810 if (!exact_solver.set_target_points(directory + "/target_points", io_coding))
1811 {
1812 pcout << "Failed to load target points for exact SOT" << std::endl;
1813 return;
1814 }
1815
1816 // Set solver parameters
1817 exact_solver.set_parameters(
1818 solver_params.max_iterations,
1819 solver_params.tolerance);
1820
1821 // Create output directory
1822 std::string output_dir = "output/exact_sot";
1823 fs::create_directories(output_dir);
1824
1825 // Run the solver
1826 if (!exact_solver.run())
1827 {
1828 pcout << "Exact SOT computation failed" << std::endl;
1829 return;
1830 }
1831
1832 // Save results
1833 if (!exact_solver.save_results(
1834 output_dir + "/potentials",
1835 output_dir + "/points"))
1836 {
1837 pcout << "Failed to save exact SOT results" << std::endl;
1838 return;
1839 }
1840
1841 pcout << Color::green << Color::bold << "Exact SOT computation completed successfully" << Color::reset << std::endl;
1842}
1843
1844template <int dim, int spacedim>
1846{
1847
1848 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
1849 {
1850 pcout << "Preparing multilevel mesh hierarchy..." << std::endl;
1851
1852 // Create MeshHierarchyManager instance
1853 MeshHierarchy::MeshHierarchyManager hierarchy_manager(
1854 multilevel_params.source_min_vertices,
1855 multilevel_params.source_max_vertices);
1856
1857 std::string source_mesh_file = "output/data_mesh/" + source_mesh_name + ".msh";
1858
1859 // Check if source mesh file exists
1860 if (!std::filesystem::exists(source_mesh_file))
1861 {
1862 pcout << "Source mesh file not found: " << source_mesh_file << std::endl;
1863 pcout << "Please ensure the source mesh is available." << std::endl;
1864 return;
1865 }
1866
1867 try
1868 {
1869 // Create output directory if it doesn't exist
1870 const std::string hierarchy_dir = multilevel_params.source_hierarchy_dir;
1871 fs::create_directories(hierarchy_dir);
1872
1873 // Determine whether to fill volume based on dimension
1874 constexpr bool fill_volume = (dim == 3);
1875 pcout << "Mesh hierarchy generation mode: " << (fill_volume ? "3D volume filling" : "2D surface mesh") << std::endl;
1876
1877 // Generate hierarchy
1878 int num_levels = hierarchy_manager.generateHierarchyFromFile(
1879 source_mesh_file,
1880 hierarchy_dir,
1881 fill_volume);
1882
1883 pcout << Color::green << Color::bold << "Successfully generated mesh hierarchy with " << num_levels << " levels" << Color::reset << std::endl;
1884 pcout << Color::green << "Mesh hierarchy saved in: " << hierarchy_dir << Color::reset << std::endl;
1885 pcout << Color::green << "Level 1 (finest) vertices: " << multilevel_params.source_max_vertices << Color::reset << std::endl;
1886 pcout << Color::green << "Coarsest level vertices: ~" << multilevel_params.source_min_vertices << Color::reset << std::endl;
1887 }
1888 catch (const std::exception &e)
1889 {
1890 pcout << Color::red << Color::bold << "Error: " << e.what() << Color::reset << std::endl;
1891 }
1892 }
1893 MPI_Barrier(mpi_communicator);
1894}
1895
1896template <int dim, int spacedim>
1898{
1899
1900 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
1901 {
1902 pcout << "Preparing target point cloud hierarchy..." << std::endl;
1903 // First ensure we have target points and density
1904 if (target_points.empty())
1905 {
1906 setup_target_finite_elements();
1907 }
1908
1909 if (target_points.empty())
1910 {
1911 pcout << Color::red << Color::bold << "Error: No target points available" << Color::reset << std::endl;
1912 return;
1913 }
1914
1915 // Create output directory
1916 fs::create_directories(multilevel_params.target_hierarchy_dir);
1917 if (multilevel_params.use_python_clustering)
1918 {
1919 // Only rank 0 executes the Python script
1920 pcout << "Using Python script for clustering: " << multilevel_params.python_script_name << std::endl;
1921
1922 // Construct the Python command
1923 std::string python_cmd = "python3 ";
1924 if (multilevel_params.python_script_name.find('/') == std::string::npos)
1925 {
1926 if (fs::exists("python_scripts/" + multilevel_params.python_script_name))
1927 {
1928 python_cmd += "python_scripts/";
1929 }
1930 }
1931 python_cmd += multilevel_params.python_script_name;
1932
1933 pcout << Color::green << Color::bold << "Executing: " << python_cmd << Color::reset << std::endl;
1934 int result = std::system(python_cmd.c_str());
1935
1936 if (result != 0)
1937 {
1938 pcout << Color::red << Color::bold << "Error: Python script execution failed with code "
1939 << result << Color::reset << std::endl;
1940 return;
1941 }
1942 pcout << Color::green << "Python script executed successfully" << Color::reset << std::endl;
1943 }
1944 else
1945 {
1946 // Use built-in C++ implementation
1948 multilevel_params.target_min_points,
1949 multilevel_params.target_max_points);
1950
1951 try
1952 {
1953
1954 pcout << "Generating hierarchy with " << target_points.size() << " points..." << std::endl;
1955
1956 // Convert dealii vector to std::vector
1957 std::vector<double> target_densities(target_points.size());
1958 for (size_t i = 0; i < target_points.size(); ++i)
1959 {
1960 target_densities[i] = target_density[i];
1961 }
1962
1963 int num_levels = hierarchy_manager.generateHierarchy<spacedim>(
1964 target_points,
1965 target_densities,
1966 multilevel_params.target_hierarchy_dir);
1967
1968 pcout << Color::green << Color::bold << "Successfully generated " << num_levels
1969 << " levels of point cloud hierarchy" << Color::reset << std::endl;
1970 }
1971 catch (const std::exception &e)
1972 {
1973 pcout << Color::red << Color::bold << "Error: " << e.what() << Color::reset << std::endl;
1974 return;
1975 }
1976 }
1977 }
1978 MPI_Barrier(mpi_communicator);
1979}
1980
1981template <int dim, int spacedim>
1982void SemiDiscreteOT<dim, spacedim>::save_results(const Vector<double> &potential, const std::string &filename, bool add_epsilon_prefix)
1983{
1984 // Only rank 0 should create directories and write files
1985 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
1986 {
1987 std::string full_path;
1988 if (add_epsilon_prefix)
1989 {
1990 // Create epsilon-specific directory
1991 std::string eps_dir = "output/epsilon_" + Utils::to_scientific_string(solver_params.epsilon);
1992 fs::create_directories(eps_dir);
1993 full_path = eps_dir + "/" + filename;
1994 }
1995 else
1996 {
1997 // Use the filename as is
1998 full_path = filename;
1999 }
2000
2001 // Save potential
2002 std::vector<double> potential_vec(potential.begin(), potential.end());
2003 Utils::write_vector(potential_vec, full_path, io_coding);
2004 }
2005 // Make sure all processes wait for rank 0 to finish writing
2006 MPI_Barrier(mpi_communicator);
2007}
2008
2009template <int dim, int spacedim>
2011{
2012 load_meshes();
2013 setup_finite_elements();
2014
2015 // Let user select which folder(s) to use
2016 std::vector<std::string> selected_folders;
2017 try
2018 {
2019 selected_folders = Utils::select_folder();
2020 }
2021 catch (const std::exception &e)
2022 {
2023 pcout << "Error: " << e.what() << std::endl;
2024 return;
2025 }
2026
2027 // Create transport map approximator
2029
2030 // Get source points and density (serial version)
2031 std::vector<Point<spacedim>> source_points;
2032 pcout << "Source density size: " << source_density.size() << std::endl;
2033
2034 const std::string directory = "output/data_points";
2035 bool points_loaded = false;
2036 if (Utils::read_vector(source_points, directory + "/source_points", io_coding))
2037 {
2038 points_loaded = true;
2039 std::cout << "Source points loaded from file" << std::endl;
2040 std::cout << "First 4 values of source density: ";
2041 }
2042
2043 if (!points_loaded)
2044 {
2045 std::map<types::global_dof_index, Point<spacedim>> support_points_source;
2046 DoFTools::map_dofs_to_support_points(*mapping, dof_handler_source, support_points_source);
2047 source_points.clear();
2048 for (const auto &point_pair : support_points_source)
2049 {
2050 source_points.push_back(point_pair.second);
2051 }
2052
2053 // Save the computed points
2054 Utils::write_vector(source_points, directory + "/source_points", io_coding);
2055 std::cout << "Source points computed and saved to file" << std::endl;
2056 }
2057
2058 // Convert densities to vectors
2059 std::vector<double> source_density_vec(source_density.begin(), source_density.end());
2060 std::vector<double> target_density_vec(target_density.begin(), target_density.end());
2061
2062 std::cout << "Setup complete with " << source_points.size() << " source points and "
2063 << target_points.size() << " target points" << std::endl;
2064
2065 // Process each selected folder
2066 for (const auto &selected_folder : selected_folders)
2067 {
2068 pcout << "\nProcessing folder: " << selected_folder << std::endl;
2069
2070 // Read potentials from selected folder's results
2071 std::vector<double> potentials_vec;
2072 bool success = Utils::read_vector(potentials_vec, "output/" + selected_folder + "/potentials", io_coding);
2073 if (!success)
2074 {
2075 pcout << "Failed to read potentials from output/" + selected_folder + "/potentials" << std::endl;
2076 continue; // Skip to next folder
2077 }
2078
2079 if (potentials_vec.size() != target_points.size())
2080 {
2081 pcout << Color::red << Color::bold << "Error: Mismatch between potentials size (" << potentials_vec.size()
2082 << ") and target points size (" << target_points.size() << ")" << Color::reset << std::endl;
2083 continue; // Skip to next folder
2084 }
2085
2086 // Extract regularization parameter from folder name
2087 std::string eps_str = selected_folder.substr(selected_folder.find("epsilon_") + 8);
2088 double epsilon = std::stod(eps_str);
2089
2090 // Create output directory
2091 const std::string output_dir = "output/" + selected_folder + "/transport_map";
2092 fs::create_directories(output_dir);
2093
2094 // Convert potentials to dealii::Vector format
2095 Vector<double> potentials_dealii(potentials_vec.size());
2096 std::copy(potentials_vec.begin(), potentials_vec.end(), potentials_dealii.begin());
2097
2098 // Set up the transport plan
2099 transport_plan.set_source_measure(source_points, source_density_vec);
2100 transport_plan.set_target_measure(target_points, target_density_vec);
2101 transport_plan.set_potential(potentials_dealii, epsilon);
2102 transport_plan.set_truncation_radius(transport_map_params.truncation_radius);
2103
2104 // Try different strategies and save results
2105 for (const auto &strategy : transport_plan.get_available_strategies())
2106 {
2107 pcout << "Computing transport map using " << strategy << " strategy..." << std::endl;
2108
2109 transport_plan.set_strategy(strategy);
2110 transport_plan.compute_map();
2111 transport_plan.save_map(output_dir + "/" + strategy);
2112
2113 pcout << "Results saved in " << output_dir + "/" + strategy << std::endl;
2114 }
2115 }
2116
2117 if (selected_folders.size() > 1)
2118 {
2119 pcout << "\nCompleted transport map computation for all selected folders." << std::endl;
2120 }
2121}
2122
2123template <int dim, int spacedim>
2125{
2126 load_meshes();
2127 setup_finite_elements();
2128
2129 // Let user select which folder(s) to use
2130 std::vector<std::string> selected_folders;
2131 try
2132 {
2133 selected_folders = Utils::select_folder();
2134 }
2135 catch (const std::exception &e)
2136 {
2137 pcout << "Error: " << e.what() << std::endl;
2138 return;
2139 }
2140
2141 // Process each selected folder
2142 for (const auto &selected_folder : selected_folders)
2143 {
2144 pcout << "\nProcessing folder: " << selected_folder << std::endl;
2145
2146 // Read potentials from selected folder's results
2147 std::vector<double> potentials_vec;
2148 bool success = Utils::read_vector(potentials_vec, "output/" + selected_folder + "/potentials", io_coding);
2149 if (!success)
2150 {
2151 pcout << "Failed to read potentials from output/" << selected_folder << "/potentials" << std::endl;
2152 continue; // Skip to next folder
2153 }
2154
2155 if (potentials_vec.size() != target_points.size())
2156 {
2157 pcout << Color::red << Color::bold << "Error: Mismatch between potentials size (" << potentials_vec.size()
2158 << ") and target points size (" << target_points.size() << ")" << Color::reset << std::endl;
2159 continue; // Skip to next folder
2160 }
2161
2162 // Convert to dealii::Vector
2163 Vector<double> potentials(potentials_vec.size());
2164 std::copy(potentials_vec.begin(), potentials_vec.end(), potentials.begin());
2165
2166 // Create output directory
2167 const std::string output_dir = "output/" + selected_folder + "/power_diagram_" + power_diagram_params.implementation;
2168 fs::create_directories(output_dir);
2169
2170 // Create power diagram using factory function based on parameter choice
2171 std::unique_ptr<PowerDiagramSpace::PowerDiagramBase<dim, spacedim>> power_diagram;
2172
2173 try
2174 {
2175 if (power_diagram_params.implementation == "geogram")
2176 {
2177 if constexpr (dim == 3 and spacedim==dim)
2178 {
2179 power_diagram = PowerDiagramSpace::create_power_diagram<dim, spacedim>(
2180 "geogram",
2181 nullptr,
2182 "output/data_mesh/source.msh");
2183 pcout << "Using Geogram implementation for power diagram" << std::endl;
2184 }
2185 else
2186 {
2187 pcout << "Geogram implementation is only available for 3D problems" << std::endl;
2188 pcout << "Falling back to Deal.II implementation" << std::endl;
2189 power_diagram = PowerDiagramSpace::create_power_diagram<dim, spacedim>(
2190 "dealii",
2191 &source_mesh);
2192 }
2193 }
2194 else
2195 {
2196 power_diagram = PowerDiagramSpace::create_power_diagram<dim, spacedim>(
2197 "dealii",
2198 &source_mesh);
2199 pcout << "Using Deal.II implementation for power diagram" << std::endl;
2200 }
2201 }
2202 catch (const std::exception &e)
2203 {
2204 pcout << Color::red << Color::bold << "Failed to initialize " << power_diagram_params.implementation
2205 << " implementation: " << e.what() << Color::reset << std::endl;
2206 if (power_diagram_params.implementation == "geogram")
2207 {
2208 pcout << "Falling back to Deal.II implementation" << std::endl;
2209 power_diagram = PowerDiagramSpace::create_power_diagram<dim, spacedim>(
2210 "dealii",
2211 &source_mesh);
2212 }
2213 else
2214 {
2215 throw;
2216 }
2217 }
2218
2219 // Set generators and compute power diagram
2220 power_diagram->set_generators(target_points, potentials);
2221 power_diagram->compute_power_diagram();
2222 power_diagram->compute_cell_centroids();
2223
2224 // Save results
2225 power_diagram->save_centroids_to_file(output_dir + "/centroids");
2226 power_diagram->output_vtu(output_dir + "/power_diagram");
2227
2228 pcout << "Power diagram computation completed for " << selected_folder << std::endl;
2229 pcout << "Results saved in " << output_dir << std::endl;
2230 }
2231
2232 if (selected_folders.size() > 1)
2233 {
2234 pcout << "\nCompleted power diagram computation for all selected folders." << std::endl;
2235 }
2236}
2237
2238template <int dim, int spacedim>
2240{
2241 load_meshes();
2242 setup_finite_elements();
2243
2244 // Set up source measure
2245 sot_solver->setup_source(dof_handler_source,
2246 *mapping,
2247 *fe_system,
2248 source_density,
2249 solver_params.quadrature_order);
2250
2251 // Set up target measure
2252 sot_solver->setup_target(target_points, target_density);
2253 sot_solver->configure(solver_params);
2254
2255 // Use indices and folder from parameters
2256 std::vector<unsigned int> indices = param_manager.conditional_density_params.indices;
2257 std::string potential_folder = param_manager.conditional_density_params.potential_folder;
2258
2259 // If indices are empty, log error and return
2260 if (indices.empty())
2261 {
2262 pcout << Color::red << Color::bold << "Error: No indices specified for conditional density computation." << Color::reset << std::endl;
2263 pcout << "Please specify indices in the parameter file under conditional_density_parameters/indices." << std::endl;
2264 return;
2265 }
2266
2267 // Determine which folder to use
2268 std::string selected_folder;
2269 if (potential_folder.empty())
2270 {
2271 // Let user select which folder to use (similar to transport map)
2272 std::vector<std::string> selected_folders;
2273 try
2274 {
2275 selected_folders = Utils::select_folder();
2276 if (selected_folders.empty())
2277 {
2278 pcout << "No folder selected. Exiting." << std::endl;
2279 return;
2280 }
2281 selected_folder = selected_folders[0]; // Use first selected folder
2282 }
2283 catch (const std::exception &e)
2284 {
2285 pcout << "Error: " << e.what() << std::endl;
2286 return;
2287 }
2288 }
2289 else
2290 {
2291 selected_folder = potential_folder;
2292 }
2293
2294 pcout << "Computing conditional densities for folder: " << selected_folder << std::endl;
2295 pcout << "Indices: ";
2296 for (size_t i = 0; i < indices.size(); ++i)
2297 {
2298 if (i > 0) pcout << ", ";
2299 pcout << indices[i];
2300 }
2301 pcout << std::endl;
2302
2303 // Read potentials from selected folder
2304 std::vector<double> potentials_vec;
2305 bool success = Utils::read_vector(potentials_vec, "output/" + selected_folder + "/potentials", io_coding);
2306 if (!success)
2307 {
2308 pcout << "Failed to read potentials from output/" + selected_folder + "/potentials" << std::endl;
2309 return;
2310 }
2311
2312 if (potentials_vec.size() != target_points.size())
2313 {
2314 pcout << Color::red << Color::bold << "Error: Mismatch between potentials size (" << potentials_vec.size()
2315 << ") and target points size (" << target_points.size() << ")" << Color::reset << std::endl;
2316 return;
2317 }
2318
2319 // Convert to dealii::Vector
2320 Vector<double> potentials(potentials_vec.size());
2321 std::copy(potentials_vec.begin(), potentials_vec.end(), potentials.begin());
2322
2323 // Validate indices
2324 for (unsigned int idx : indices)
2325 {
2326 if (idx >= target_points.size())
2327 {
2328 pcout << Color::red << Color::bold << "Error: Index " << idx << " is out of bounds. Maximum index is "
2329 << target_points.size() - 1 << "." << Color::reset << std::endl;
2330 return;
2331 }
2332 }
2333
2334 // Compute conditional densities using SOT solver
2335 std::vector<LinearAlgebra::distributed::Vector<double, MemorySpace::Host>> conditioned_densities;
2336
2337 // Set truncation radius if specified
2338 if (param_manager.conditional_density_params.truncation_radius > 0.0)
2339 {
2340 pcout << "Using truncation radius: " << param_manager.conditional_density_params.truncation_radius << std::endl;
2341 sot_solver->set_distance_threshold(param_manager.conditional_density_params.truncation_radius);
2342 }
2343
2344 pcout << "Computing conditional densities..." << std::endl;
2345 sot_solver->get_potential_conditioned_density(
2346 dof_handler_source, *mapping,
2347 potentials, indices, conditioned_densities);
2348
2349 // Create output directory
2350 const std::string output_dir = "output/" + selected_folder + "/conditional_densities";
2351 fs::create_directories(output_dir);
2352
2353 // Save conditional densities
2354 DataOut<dim, spacedim> data_out;
2355 data_out.attach_dof_handler(dof_handler_source);
2356
2357 std::vector<DataComponentInterpretation::DataComponentInterpretation>
2358 interpretation(1, DataComponentInterpretation::component_is_scalar);
2359
2360 pcout << "Saving " << conditioned_densities.size() << " conditional densities..." << std::endl;
2361
2362 for (unsigned int i = 0; i < conditioned_densities.size(); ++i)
2363 {
2364 data_out.add_data_vector(
2365 conditioned_densities[i],
2366 std::string("conditional_density_") + std::to_string(indices[i]),
2367 DataOut<dim, spacedim>::type_dof_data,
2368 interpretation);
2369 }
2370
2371 data_out.build_patches(*mapping, fe_system->degree);
2372
2373 const std::string filename_base = "conditional_densities";
2374 data_out.write_vtu_with_pvtu_record(output_dir + "/",
2375 filename_base,
2376 0,
2377 mpi_communicator);
2378
2379 pcout << "Conditional densities saved to " << output_dir << "/" << filename_base << "_0.pvtu" << std::endl;
2380
2381 // Save individual VTU files for each conditional density
2382 for (unsigned int i = 0; i < conditioned_densities.size(); ++i)
2383 {
2384 DataOut<dim, spacedim> individual_data_out;
2385 individual_data_out.attach_dof_handler(dof_handler_source);
2386 individual_data_out.add_data_vector(
2387 conditioned_densities[i],
2388 std::string("conditional_density_") + std::to_string(indices[i]),
2389 DataOut<dim, spacedim>::type_dof_data,
2390 interpretation);
2391 individual_data_out.build_patches(*mapping, fe_system->degree);
2392
2393 const std::string individual_filename = "conditional_density_" + std::to_string(indices[i]);
2394 individual_data_out.write_vtu_with_pvtu_record(output_dir + "/",
2395 individual_filename,
2396 0,
2397 mpi_communicator);
2398 }
2399
2400 pcout << "Conditional density computation completed successfully." << std::endl;
2401}
2402
2403template <int dim, int spacedim>
2405{
2406 load_meshes();
2407 setup_finite_elements();
2408
2409 // Create output directory
2410 const std::string directory = "output/discrete_measures";
2411 fs::create_directories(directory);
2412
2413 // Get quadrature points and weights
2414 auto quadrature = Utils::create_quadrature_for_mesh<dim, spacedim>(source_mesh, solver_params.quadrature_order);
2415
2416 FEValues<dim, spacedim> fe_values(*mapping, *fe_system, *quadrature,
2417 update_values | update_quadrature_points | update_JxW_values);
2418
2419 // Count total number of quadrature points
2420 const unsigned int n_q_points = quadrature->size();
2421 const unsigned int total_q_points = source_mesh.n_active_cells() * n_q_points;
2422
2423 // Prepare vectors for quadrature data
2424 std::vector<Point<spacedim>> quad_points;
2425 std::vector<double> quad_weights;
2426 std::vector<double> density_values;
2427 quad_points.reserve(total_q_points);
2428 quad_weights.reserve(total_q_points);
2429 density_values.reserve(total_q_points);
2430
2431 // Get density values at DoF points
2432 std::vector<double> local_density_values(n_q_points);
2433
2434 // Loop over cells to collect quadrature data
2435 for (const auto &cell : dof_handler_source.active_cell_iterators())
2436 {
2437 fe_values.reinit(cell);
2438 fe_values.get_function_values(source_density, local_density_values);
2439
2440 for (unsigned int q = 0; q < n_q_points; ++q)
2441 {
2442 quad_points.push_back(fe_values.quadrature_point(q));
2443 quad_weights.push_back(fe_values.JxW(q));
2444 density_values.push_back(local_density_values[q]);
2445 }
2446 }
2447
2448 // Save quadrature data
2449 Utils::write_vector(quad_points, directory + "/quadrature_points", io_coding);
2450 Utils::write_vector(quad_weights, directory + "/quadrature_weights", io_coding);
2451 Utils::write_vector(density_values, directory + "/density_values", io_coding);
2452
2453 // Save target measure data
2454 Utils::write_vector(target_points, directory + "/target_points", io_coding);
2455 std::vector<double> target_density_vec(target_density.begin(), target_density.end());
2456 Utils::write_vector(target_density_vec, directory + "/target_density", io_coding);
2457
2458 // Save metadata
2459 std::ofstream meta(directory + "/metadata.txt");
2460 meta << "Dimension: " << dim << "\n"
2461 << "Space dimension: " << spacedim << "\n"
2462 << "Number of quadrature points per cell: " << n_q_points << "\n"
2463 << "Total number of quadrature points: " << total_q_points << "\n"
2464 << "Number of target points: " << target_points.size() << "\n"
2465 << "Quadrature order: " << solver_params.quadrature_order << "\n"
2466 << "Using tetrahedral mesh: " << source_params.use_tetrahedral_mesh << "\n";
2467 meta.close();
2468
2469 pcout << "Discrete measures data saved in " << directory << std::endl;
2470 pcout << "Total quadrature points: " << total_q_points << std::endl;
2471 pcout << "Number of target points: " << target_points.size() << std::endl;
2472}
2473
2474template <int dim, int spacedim>
2476{
2477 pcout << Color::yellow << Color::bold << "Starting field interpolation visualization..." << Color::reset << std::endl;
2478
2479 std::string output_dir = "output/interpolated_fields";
2480 fs::create_directories(output_dir);
2481
2482 std::string source_field_name = source_params.density_field_name;
2483 std::string target_field_name = target_params.density_field_name;
2484
2485 pcout << "Using source field name: " << source_field_name << std::endl;
2486 pcout << "Using target field name: " << target_field_name << std::endl;
2487
2488 pcout << "\n"
2489 << Color::cyan << Color::bold << "Processing base source mesh" << Color::reset << std::endl;
2490
2491 mesh_manager->load_source_mesh(source_mesh);
2492 setup_source_finite_elements(false); // false = not multilevel
2493
2494 std::string base_source_dir = output_dir + "/base_source";
2495 fs::create_directories(base_source_dir);
2496
2497 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
2498 {
2499 DataOut<dim, spacedim> data_out;
2500 data_out.attach_dof_handler(dof_handler_source);
2501 data_out.add_data_vector(source_density, source_field_name);
2502 data_out.build_patches();
2503
2504 std::string vtk_filename = base_source_dir + "/" + source_field_name + ".vtk";
2505 std::ofstream output(vtk_filename);
2506 data_out.write_vtk(output);
2507
2508 pcout << "Saved interpolated field for base source mesh to: " << vtk_filename << std::endl;
2509 }
2510
2511 pcout << "\n"
2512 << Color::cyan << Color::bold << "Processing base target mesh" << Color::reset << std::endl;
2513
2514 mesh_manager->load_target_mesh(target_mesh);
2515 setup_target_finite_elements();
2516
2517 std::string base_target_dir = output_dir + "/base_target";
2518 fs::create_directories(base_target_dir);
2519
2520 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
2521 {
2522 DataOut<dim, spacedim> data_out;
2523 data_out.attach_dof_handler(dof_handler_target);
2524 data_out.add_data_vector(target_density, target_field_name);
2525 data_out.build_patches();
2526
2527 std::string vtk_filename = base_target_dir + "/" + target_field_name + ".vtk";
2528 std::ofstream output(vtk_filename);
2529 data_out.write_vtk(output);
2530
2531 pcout << "Saved interpolated field for base target mesh to: " << vtk_filename << std::endl;
2532 }
2533
2534 if (multilevel_params.source_enabled)
2535 {
2536 pcout << "\n"
2537 << Color::cyan << Color::bold << "Processing multilevel source meshes" << Color::reset << std::endl;
2538
2539 std::vector<std::string> source_mesh_files;
2540 unsigned int num_levels = 0;
2541
2542 source_mesh_files = mesh_manager->get_mesh_hierarchy_files(multilevel_params.source_hierarchy_dir);
2543 if (source_mesh_files.empty())
2544 {
2545 pcout << Color::red << Color::bold << "No source mesh hierarchy found. Please run prepare_source_multilevel first." << Color::reset << std::endl;
2546 }
2547 else
2548 {
2549 num_levels = source_mesh_files.size();
2550 pcout << "Found " << num_levels << " levels in the source mesh hierarchy" << std::endl;
2551
2552 for (size_t level = 0; level < source_mesh_files.size(); ++level)
2553 {
2554 const unsigned int level_name = num_levels - level - 1;
2555 pcout << "\n"
2557 << "Processing source mesh level " << level_name
2558 << " (mesh: " << source_mesh_files[level] << ")" << Color::reset << std::endl;
2559
2560 std::string level_dir = output_dir + "/source_level_" + std::to_string(level_name);
2561 fs::create_directories(level_dir);
2562
2563 mesh_manager->load_mesh_at_level(source_mesh, dof_handler_source, source_mesh_files[level]);
2564
2565 setup_source_finite_elements(true);
2566
2567 if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
2568 {
2569 DataOut<dim, spacedim> data_out;
2570 data_out.attach_dof_handler(dof_handler_source);
2571 data_out.add_data_vector(source_density, source_field_name);
2572 data_out.build_patches();
2573
2574 std::string vtk_filename = level_dir + "/" + source_field_name + ".vtk";
2575 std::ofstream output(vtk_filename);
2576 data_out.write_vtk(output);
2577
2578 pcout << "Saved interpolated field for source level " << level_name
2579 << " to: " << vtk_filename << std::endl;
2580 }
2581 }
2582 }
2583 }
2584
2585 pcout << "\n"
2587 << "Field interpolation visualization completed!" << Color::reset << std::endl;
2588 pcout << "Results saved in: " << output_dir << std::endl;
2589}
2590
2591template <int dim, int spacedim>
2593{
2594 param_manager.print_parameters();
2595
2596 if (solver_params.use_epsilon_scaling)
2597 {
2598 epsilon_scaling_handler = std::make_unique<EpsilonScalingHandler>(
2599 mpi_communicator,
2600 solver_params.epsilon,
2601 solver_params.epsilon_scaling_factor,
2602 solver_params.epsilon_scaling_steps);
2603 }
2604
2605 if (selected_task == "mesh_generation")
2606 {
2607 mesh_generation();
2608 }
2609 else if (selected_task == "load_meshes")
2610 {
2611 load_meshes();
2612 }
2613 else if (selected_task == "sot")
2614 {
2615 load_meshes();
2616 Vector<double> potentials = run_sot();
2617 }
2618 else if (selected_task == "prepare_source_multilevel")
2619 {
2620 prepare_source_multilevel();
2621 }
2622 else if (selected_task == "prepare_target_multilevel")
2623 {
2624 mesh_manager->load_target_mesh(target_mesh);
2625 prepare_target_multilevel();
2626 }
2627 else if (selected_task == "prepare_multilevel")
2628 {
2629 if (multilevel_params.source_enabled)
2630 prepare_source_multilevel();
2631 if (multilevel_params.target_enabled)
2632 {
2633 mesh_manager->load_target_mesh(target_mesh);
2634 prepare_target_multilevel();
2635 }
2636 }
2637 else if (selected_task == "target_multilevel")
2638 {
2639 Vector<double> potentials = run_target_multilevel();
2640 }
2641 else if (selected_task == "source_multilevel")
2642 {
2643 Vector<double> potentials = run_source_multilevel();
2644 }
2645 else if (selected_task == "multilevel")
2646 {
2647 Vector<double> potentials = run_multilevel();
2648 }
2649 else if (selected_task == "exact_sot")
2650 {
2651 if constexpr (dim == 3 && spacedim==3) {
2652 load_meshes();
2653 setup_finite_elements();
2654 run_exact_sot();
2655 }
2656 else
2657 {
2658 pcout << "Exact SOT is only available for 3D problems" << std::endl;
2659 }
2660 }
2661 else if (selected_task == "power_diagram")
2662 {
2663 compute_power_diagram();
2664 }
2665 else if (selected_task == "map")
2666 {
2667 compute_transport_map();
2668 }
2669 else if (selected_task == "conditional_density")
2670 {
2671 compute_conditional_density();
2672 }
2673 else if (selected_task == "save_discrete_measures")
2674 {
2675 save_discrete_measures();
2676 }
2677 else if (selected_task == "save_interpolated_fields")
2678 {
2679 save_interpolated_fields();
2680 }
2681 else
2682 {
2683 pcout << "No valid task selected" << std::endl;
2684 }
2685}
2686
2687
2688// Explicit template instantiation
2689template class SemiDiscreteOT<2>;
2690template class SemiDiscreteOT<3>;
2691template class SemiDiscreteOT<2, 3>;
Class to handle exact semi-discrete optimal transport using Geogram.
Definition ExactSot.h:39
bool set_source_mesh(const std::string &filename)
Set source mesh from file.
Definition ExactSot.cc:79
bool save_results(const std::string &potential_file, const std::string &points_file, const std::string &io_coding="txt") const
Save computation results to files.
Definition ExactSot.cc:161
void set_parameters(unsigned int max_iterations=1000, double epsilon=0.01)
Set parameters for the solver.
Definition ExactSot.cc:87
bool set_target_points(const std::string &filename, const std::string &io_coding)
Set target points from file.
Definition ExactSot.cc:83
bool run()
Run the exact SOT computation.
Definition ExactSot.cc:93
Class to manage a hierarchy of meshes with different resolutions.
int generateHierarchyFromFile(const std::string &input_mesh_file, const std::string &output_dir, bool fill_volume=true)
Generate hierarchy of meshes from input mesh file.
A class for computing and managing optimal transport map approximations.
void compute_map()
Compute the optimal transport map approximation using the current strategy.
static std::vector< std::string > get_available_strategies()
Get available strategy names.
void set_strategy(const std::string &strategy_name)
Change the approximation strategy.
void set_target_measure(const std::vector< Point< spacedim > > &points, const std::vector< double > &density)
Set the target measure data.
void set_source_measure(const std::vector< Point< spacedim > > &points, const std::vector< double > &density)
Set the source measure data.
void set_truncation_radius(double radius)
Set the truncation radius for map computation. Points outside this radius will not be considered in t...
void set_potential(const Vector< double > &potential, const double regularization_param=0.0)
Set the optimal transport potential.
void save_map(const std::string &output_dir) const
Save the computed transport map to files.
Class to manage a hierarchy of point clouds with different resolutions The hierarchy is organized wit...
int generateHierarchy(const std::vector< Point< spacedim > > &input_points, const std::vector< double > &input_density, const std::string &output_dir)
Generate hierarchy of point clouds from input points.
Main class for the semi-discrete optimal transport solver.
void save_discrete_measures()
Saves the discrete source and target measures to files.
void mesh_generation()
Generates the source and target meshes.
void prepare_target_multilevel()
Pre-computes the multilevel hierarchy for the target.
std::vector< std::pair< std::string, std::string > > get_target_hierarchy_files() const
Gets the target hierarchy files.
std::enable_if< d==3 &&s==3 >::type run_exact_sot()
Runs the exact semi-discrete optimal transport solver.
void save_interpolated_fields()
void load_meshes()
Loads the source and target meshes from files.
void normalize_density(LinearAlgebra::distributed::Vector< double > &density)
Normalizes the density vector.
void configure(std::function< void(SotParameterManager &)> config_func)
Configure the solver parameters programmatically.
void setup_source_measure(Triangulation< dim, spacedim > &tria, const DoFHandler< dim, spacedim > &dh, const Vector< double > &density, const std::string &name="source")
Setup source measure from standard deal.II objects (simplified API for tutorials)
void assign_potentials_by_hierarchy(Vector< double > &potentials, int coarse_level, int fine_level, const Vector< double > &prev_potentials)
Assigns potentials by hierarchy.
Vector< double > run_source_multilevel(const Vector< double > &initial_potential=Vector< double >())
Run source-only multilevel SOT computation.
Vector< double > run_multilevel(const Vector< double > &initial_potential=Vector< double >())
Run multilevel SOT computation (dispatcher method).
void setup_target_finite_elements()
Sets up the finite elements for the target mesh.
void run()
Runs the solver with the current configuration.
void compute_power_diagram()
Computes the power diagram of the target points.
void setup_finite_elements()
Sets up the finite elements for both the source and target meshes.
void compute_conditional_density()
Computes the conditional density of the source measure.
void compute_transport_map()
Computes the transport map from the source to the target measure.
SemiDiscreteOT(const MPI_Comm &mpi_communicator)
Constructor for the SemiDiscreteOT class.
void load_hierarchy_data(const std::string &hierarchy_dir, int specific_level=-1)
Loads the hierarchy data from a directory.
Vector< double > run_target_multilevel(const Vector< double > &initial_potential=Vector< double >())
Run target-only multilevel SOT computation.
void load_target_points_at_level(const std::string &points_file, const std::string &density_file)
Loads the target points at a specific level.
void setup_target_points()
Sets up the target points.
void save_results(const Vector< double > &potentials, const std::string &filename, bool add_epsilon_prefix=true)
Saves the results of the computation.
Vector< double > solve(const Vector< double > &initial_potential=Vector< double >())
Run the optimal transport computation based on the current configuration. This method handles single-...
Vector< double > run_sot(const Vector< double > &initial_potential=Vector< double >())
Run single-level SOT computation.
void setup_target_measure(const std::vector< Point< spacedim > > &points, const Vector< double > &weights)
Set up the target measure from a discrete set of points and weights.
void setup_source_finite_elements(bool is_multilevel=false)
Sets up the finite elements for the source mesh.
void prepare_multilevel_hierarchies()
Pre-computes the multilevel hierarchies for source and/or target. This must be called after setting u...
Vector< double > run_combined_multilevel(const Vector< double > &initial_potential=Vector< double >())
Run combined source and target multilevel SOT computation.
void prepare_source_multilevel()
Pre-computes the multilevel hierarchy for the source.
A class for refining the optimal transport potential using a softmax operation.
Vector< double > compute_refinement(const std::vector< Point< spacedim > > &target_points_fine, const Vector< double > &target_density_fine, const std::vector< Point< spacedim > > &target_points_coarse, const Vector< double > &target_density_coarse, const Vector< double > &potential_coarse, double regularization_param, int current_level, const std::vector< std::vector< std::vector< size_t > > > &child_indices)
Computes the refined potential.
A solver for semi-discrete optimal transport problems.
Definition SotSolver.h:55
A handler class for reading and interpolating data from VTK files.
Definition VtkHandler.h:34
void setup_field(const std::string &field_name, const DataLocation data_location=DataLocation::PointData, const unsigned int component=0)
Sets up the field to be used for interpolation.
Definition VtkHandler.cc:94
const std::string reset
const std::string yellow
const std::string magenta
const std::string cyan
const std::string blue
const std::string red
const std::string bold
const std::string green
void write_vector(const VectorContainer &points, const std::string &filepath, const std::string &fileMode="txt")
Write a vector container to a file in binary or text format.
Definition utils.h:56
std::vector< std::pair< std::string, std::string > > get_target_hierarchy_files(const std::string &dir)
Get target point cloud hierarchy files.
Definition utils.h:238
bool read_vector(VectorContainer &points, const std::string &filepath, const std::string &fileMode="")
Read a vector container from a file in binary or text format.
Definition utils.h:108
std::string to_scientific_string(double value, int precision=6)
Convert a double to a string in scientific notation.
Definition utils.h:42
void interpolate_non_conforming_nearest(const dealii::DoFHandler< dim, spacedim > &source_dh, const dealii::Vector< double > &source_field, const dealii::DoFHandler< dim, spacedim > &target_dh, dealii::LinearAlgebra::distributed::Vector< double > &target_field)
Interpolate a field from a source mesh to a target mesh using nearest neighbor approach.
Definition utils.h:504
std::vector< std::string > select_folder(const std::string &base_dir="output", bool allow_all=true)
List available epsilon folders and allow user selection.
Definition utils.h:184
unsigned int epsilon_scaling_steps
Number of scaling steps.
unsigned int quadrature_order
Order of quadrature formula.
bool use_epsilon_scaling
Enable epsilon scaling strategy.
double epsilon_scaling_factor
Factor for epsilon reduction.
double epsilon
Entropy regularization parameter.