deal.II version GIT relicensing-6750-g1dc21bc838 2026-09-15 17:20:01+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
parallel_flow_routing.h
Go to the documentation of this file.
1
303 *  
304 *   #include <deal.II/base/conditional_ostream.h>
305 *   #include <deal.II/base/function.h>
306 *   #include <deal.II/base/function_lib.h>
307 *   #include <deal.II/base/index_set.h>
308 *   #include <deal.II/base/parameter_acceptor.h>
309 *   #include <deal.II/base/quadrature_lib.h>
310 *   #include <deal.II/base/timer.h>
311 *   #include <deal.II/base/utilities.h>
312 *  
313 *   #include <deal.II/distributed/grid_refinement.h>
314 *   #include <deal.II/distributed/tria.h>
315 *  
316 *   #include <deal.II/dofs/dof_handler.h>
317 *   #include <deal.II/dofs/dof_renumbering.h>
318 *   #include <deal.II/dofs/dof_tools.h>
319 *  
320 *   #include <deal.II/fe/fe_q.h>
321 *   #include <deal.II/fe/fe_system.h>
322 *   #include <deal.II/fe/fe_values.h>
323 *  
324 *   #include <deal.II/grid/grid_generator.h>
325 *   #include <deal.II/grid/grid_tools.h>
326 *  
327 *   #include <deal.II/lac/dynamic_sparsity_pattern.h>
328 *   #include <deal.II/lac/full_matrix.h>
329 *   #include <deal.II/lac/generic_linear_algebra.h>
330 *   #include <deal.II/lac/solver_richardson.h>
331 *   #include <deal.II/lac/vector.h>
332 *  
333 *   #include <deal.II/numerics/data_out.h>
334 *   #include <deal.II/numerics/error_estimator.h>
335 *   #include <deal.II/numerics/vector_tools.h>
336 *  
337 *   #include <boost/archive/binary_iarchive.hpp>
338 *   #include <boost/archive/binary_oarchive.hpp>
339 *   #include <boost/iostreams/device/file.hpp>
340 *   #include <boost/iostreams/filter/gzip.hpp>
341 *   #include <boost/iostreams/filtering_stream.hpp>
342 *  
343 *   #include <cmath>
344 *   #include <filesystem>
345 *   #include <fstream>
346 *   #include <iostream>
347 *   #include <random>
348 *  
349 *  
350 *  
351 * @endcode
352 *
353 * This namespace contains the implementation of the parallel flow routing
354 * program. The program solves a model of water flow on terrain using the
355 * "downhill flow" method commonly used in hydrology. In this approach, water
356 * flows from each grid point in the steepest downhill direction determined by
357 * the terrain elevation, and the water flux distribution is computed by solving
358 * a steady-state flow conservation system in parallel.
359 *
360 * @code
361 *   namespace ParallelFlowRouting
362 *   {
363 *   using namespace dealii;
364 *  
365 * @endcode
366 *
367 * The LA namespace encapsulates the linear algebra library configuration.
368 * We use either PETSc or Trilinos for distributed sparse matrices and
369 * vectors, depending on what deal.II was compiled with. PETSc is preferred
370 * if available (and not using complex numbers), otherwise we fall back to
371 * Trilinos. This choice allows for efficient parallel solving of the large
372 * sparse systems that arise from discretizing the flow conservation
373 * equations.
374 *
375 * @code
376 *   namespace LA
377 *   {
378 *   #if defined(DEAL_II_WITH_PETSC) && !defined(DEAL_II_PETSC_WITH_COMPLEX) && \
379 *   !(defined(DEAL_II_WITH_TRILINOS) && defined(FORCE_USE_OF_TRILINOS))
380 *   using namespace LinearAlgebraPETSc;
381 *   # define USE_PETSC_LA
382 *   #elif defined(DEAL_II_WITH_TRILINOS)
383 *   using namespace LinearAlgebraTrilinos;
384 *   #else
385 *   # error DEAL_II_WITH_PETSC or DEAL_II_WITH_TRILINOS required
386 *   #endif
387 *   } // namespace LA
388 *  
389 * @endcode
390 *
391 * We use block vectors and block sparse matrices to represent the distributed
392 * linear systems. Block structures allow us to organize data logically and
393 * can improve performance in certain scenarios.
394 *
395 * @code
396 *   using VectorType = LA::MPI::BlockVector;
397 *   using MatrixType = LA::MPI::BlockSparseMatrix;
398 *  
399 *  
400 * @endcode
401 *
402 *
403 * <a name="parallel_flow_routing.cc-ColoradoTopography"></a>
404 * <h3>ColoradoTopography</h3>
405 *
406
407 *
408 * This class represents the topographic elevation data for Colorado, defined
409 * on the domain spanning 7 degrees longitude by 4 degrees latitude
410 * (from 109°W to 102°W and from 37°N to 41°N). The class reads elevation data
411 * from a gzip-compressed file, processes it to ensure it is suitable for flow
412 * routing (by filling depressions), and then caches the processed data for
413 * reuse in subsequent runs.
414 *
415
416 *
417 * The elevation data is provided at a baseline resolution of 1800 meters per
418 * pixel. The n_refinements parameter allows scaling to finer resolutions by
419 * subdividing each baseline grid cell into 2^n_refinements smaller cells.
420 * This is useful for studying the flow routing algorithm at different
421 * resolutions.
422 *
423
424 *
425 * The class inherits from the deal.II Function class, so it can be used
426 * directly with the VectorTools::interpolate() function to set elevation
427 * values on a finite element mesh. The function is evaluated on 3D mesh
428 * points by first converting Cartesian coordinates (used on the mesh) to
429 * geographic coordinates (longitude and latitude in degrees), and then
430 * interpolating the stored elevation data.
431 *
432
433 *
434 * A key feature of this class is the removal of local depressions (sinks)
435 * from the elevation data. These depressions are problematic for flow routing
436 * because water trapped in them does not flow anywhere. The class uses a
437 * priority-flood depression-filling algorithm to ensure that the resulting
438 * digital elevation model (DEM) has no interior depressions. This is done
439 * during construction in parallel, with process 0 performing the computation
440 * and broadcasting the result to all other processes.
441 *
442 * @code
443 *   class ColoradoTopography : public Function<3>
444 *   {
445 *   public:
446 * @endcode
447 *
448 * Constructor that loads and processes the Colorado topography data.
449 * - `mpi_communicator`: The MPI communicator used for parallel
450 * communication. Process 0 reads and processes the data, then
451 * broadcasts it to all others.
452 * - `n_refinements`: The refinement level. The mesh will have
453 * 7*2^n_refinements by 4*2^n_refinements cells.
454 *
455 * @code
456 *   ColoradoTopography(const MPI_Comm mpi_communicator,
457 *   const unsigned int n_refinements);
458 *  
459 * @endcode
460 *
461 * Return the elevation in meters at a given 3D point on the Earth's
462 * surface. The point is first converted from Cartesian coordinates (as used
463 * on the mesh) to geographic coordinates (longitude and latitude in
464 * degrees), and then the stored elevation data is interpolated.
465 *
466 * @code
467 *   virtual double
468 *   value(const Point<3> &p,
469 *   const unsigned int /*component*/ = 0) const override;
470 *  
471 *   private:
472 * @endcode
473 *
474 * The InterpolatedUniformGridData object stores the actual elevation values
475 * on a uniform grid in longitude-latitude space.
476 *
477 * @code
478 *   std::unique_ptr<const Functions::InterpolatedUniformGridData<2>> data;
479 *  
480 * @endcode
481 *
482 * Count the number of local depressions in an elevation table. A depression
483 * is a grid point that is lower than all of its 8 neighbors (including
484 * diagonals).
485 *
486 * @code
487 *   static unsigned int
488 *   count_depressions(const Table<2, double> &elevation_data);
489 *  
490 * @endcode
491 *
492 * Fill (eliminate) local depressions in an elevation table using a
493 * priority-flood algorithm. After this function completes, no interior grid
494 * point will be lower than all of its 8 neighbors.
495 *
496 * @code
497 *   static void
498 *   fill_depressions(Table<2, double> &elevation_data);
499 *   };
500 *  
501 *  
502 * @endcode
503 *
504 *
505 * <a name="parallel_flow_routing.cc-ColoradoTopographyColoradoTopography"></a>
506 * <h3>ColoradoTopography::ColoradoTopography()</h3>
507 *
508
509 *
510 * The constructor is responsible for loading the Colorado topography data.
511 * The procedure it follows is:
512 *
513
514 *
515 * 1. On process 0 (the root MPI process), check if a cache file exists for
516 * the requested resolution. If not, read the original data from a
517 * gzip-compressed file, interpolate it to the desired resolution, fill
518 * depressions, and cache the result in a binary-serialized file. If the
519 * cache exists, read directly from it (this is much faster).
520 *
521
522 *
523 * 2. Broadcast the elevation data to all MPI processes.
524 *
525
526 *
527 * 3. Create an InterpolatedUniformGridData object that can efficiently
528 * evaluate elevation at arbitrary points via bilinear interpolation.
529 *
530
531 *
532 * The input data file is expected to be in ESRI ASCII raster format,
533 * compressed with gzip. The format includes header lines specifying the
534 * number of columns and rows, the corner coordinates, and the cell size.
535 *
536 * @code
537 *   ColoradoTopography::ColoradoTopography(const MPI_Comm mpi_communicator,
538 *   const unsigned int n_refinements)
539 *   {
540 *   unsigned int n_latitudes = numbers::invalid_unsigned_int;
541 *   unsigned int n_longitudes = numbers::invalid_unsigned_int;
542 *   Point<2> lower_left_corner = numbers::signaling_nan<Point<2>>();
543 *   double pixel_size = numbers::signaling_nan<double>();
544 *  
545 *   Table<2, double> elevation_data;
546 *  
547 *   const unsigned int root_process = 0;
548 *   if (Utilities::MPI::this_mpi_process(mpi_communicator) == root_process)
549 *   {
550 *   const std::string cache_filename =
551 *   "colorado-topography-1800m.cache" + std::to_string(n_refinements);
552 *  
553 *   if (!std::filesystem::exists(cache_filename))
554 *   {
555 *   const std::string original_data_filename =
556 *   "colorado-topography-1800m.txt.gz";
557 *   std::cout << " Reading original elevation data from file "
558 *   << original_data_filename << std::endl;
559 *  
560 *   unsigned int n_original_latitudes;
561 *   unsigned int n_original_longitudes;
562 *   Point<2> original_lower_left_corner;
563 *   double original_pixel_size;
564 *  
565 *   Table<2, double> original_elevation_data;
566 *  
567 *   boost::iostreams::filtering_istream in;
568 *   in.push(boost::iostreams::basic_gzip_decompressor<>());
569 *   in.push(boost::iostreams::file_source(original_data_filename));
570 *  
571 *   std::string word;
572 *  
573 *   in >> word;
574 *   AssertThrow(word == "ncols",
575 *   ExcMessage(
576 *   "The first line of the input file needs to start "
577 *   "with the word 'ncols', but starts with '" +
578 *   word + "'."));
579 *   in >> n_original_longitudes;
580 *  
581 *   in >> word;
582 *   AssertThrow(word == "nrows",
583 *   ExcMessage(
584 *   "The second line of the input file needs to start "
585 *   "with the word 'nrows', but starts with '" +
586 *   word + "'."));
587 *   in >> n_original_latitudes;
588 *  
589 *   in >> word;
590 *   AssertThrow(word == "xllcorner",
591 *   ExcMessage(
592 *   "The third line of the input file needs to start "
593 *   "with the word 'xllcorner', but starts with '" +
594 *   word + "'."));
595 *   in >> original_lower_left_corner[0];
596 *  
597 *   in >> word;
598 *   AssertThrow(word == "yllcorner",
599 *   ExcMessage(
600 *   "The fourth line of the input file needs to start "
601 *   "with the word 'yllcorner', but starts with '" +
602 *   word + "'."));
603 *   in >> original_lower_left_corner[1];
604 *  
605 *  
606 *   in >> word;
607 *   AssertThrow(word == "cellsize",
608 *   ExcMessage(
609 *   "The fourth line of the input file needs to start "
610 *   "with the word 'cellsize', but starts with '" +
611 *   word + "'."));
612 *   in >> original_pixel_size;
613 *  
614 *   original_elevation_data.reinit(n_original_longitudes,
615 *   n_original_latitudes);
616 *  
617 * @endcode
618 *
619 * Data is provided in the input file as horizontal strips, with
620 * longitude marching fastest from west to east. The second
621 * coordinate is latitude, but the file marches north to south,
622 * which is the opposite of how we want it (we want a right-handed
623 * coordinate system), so we have to revert the order in which we
624 * insert things into the data table.
625 *
626 * @code
627 *   for (unsigned int latitude_index = 0;
628 *   latitude_index < n_original_latitudes;
629 *   ++latitude_index)
630 *   for (unsigned int longitude_index = 0;
631 *   longitude_index < n_original_longitudes;
632 *   ++longitude_index)
633 *   {
634 *   try
635 *   {
636 *   double elevation;
637 *   in >> elevation;
638 *  
639 *   original_elevation_data(longitude_index,
640 *   n_original_latitudes -
641 *   latitude_index - 1) = elevation;
642 *   }
643 *   catch (...)
644 *   {
645 *   AssertThrow(false,
646 *   ExcMessage(
647 *   "Could not read all expected data points "
648 *   "from the file <" +
649 *   original_data_filename + ">!"));
650 *   }
651 *   }
652 *  
653 *   const Functions::InterpolatedUniformGridData<2>
654 *   original_elevation_field(
655 *   std::array<std::pair<double, double>, 2>{
656 *   {std::make_pair(original_lower_left_corner[0],
657 *   original_lower_left_corner[0] +
658 *   (n_original_longitudes - 1) *
659 *   original_pixel_size),
660 *   std::make_pair(original_lower_left_corner[1],
661 *   original_lower_left_corner[1] +
662 *   (n_original_latitudes - 1) *
663 *   original_pixel_size)}},
664 *   std::array<unsigned int, 2>{
665 *   {n_original_longitudes - 1, n_original_latitudes - 1}},
666 *   std::move(original_elevation_data));
667 *  
668 * @endcode
669 *
670 * The model that we just read is provided on a domain that is
671 * slightly larger than what we actually need, and on a mesh that
672 * does not align with the vertices we will create later on. It may
673 * also contain local depressions that prevent us from performing
674 * useful flow routing, and even if it doesn't, the interpolation
675 * onto a concrete mesh that has different vertices will create a
676 * model with local depressions.
677 *
678
679 *
680 * To avoid this, we take the following steps where 'r' is a
681 * parameter that controls the resolution of the mesh we will
682 * create:
683 * * We interpolate things onto a mesh that has 7*2^r x 4*2^r cells
684 * (i.e., 7*2^r+1 x 4*2^r+1 points) and that has the exact right
685 * extents. This makes sense because below we will
686 * start all computations on a 7x4 mesh, given that Colorado spans
687 * 7 by 4 degrees on the surface of the Earth.
688 * * We then perform depression filling by lifting points that are
689 * lower than all of their neighbors above the lowest of its
690 * neighbors. The result is a digital elevation model without
691 * local depressions.
692 *
693 * @code
694 *   const unsigned int n_subdivisions = (1 << n_refinements);
695 *   n_longitudes = 7 * n_subdivisions + 1;
696 *   n_latitudes = 4 * n_subdivisions + 1;
697 *   pixel_size = 1. / n_subdivisions;
698 *   lower_left_corner = {-109., 37.}; // 109 degrees W, 37 degrees N
699 *  
700 *   elevation_data.reinit(n_longitudes, n_latitudes);
701 *   for (unsigned int latitude_index = 0; latitude_index < n_latitudes;
702 *   ++latitude_index)
703 *   for (unsigned int longitude_index = 0;
704 *   longitude_index < n_longitudes;
705 *   ++longitude_index)
706 *   {
707 *   const double longitude =
708 *   lower_left_corner[0] + longitude_index * pixel_size;
709 *   const double latitude =
710 *   lower_left_corner[1] + latitude_index * pixel_size;
711 *   elevation_data(longitude_index, latitude_index) =
712 *   original_elevation_field.value(
713 *   Point<2>(longitude, latitude));
714 *   }
715 *  
716 * @endcode
717 *
718 * Now we need to fix up depressions in the *interior* of the table.
719 *
720 * @code
721 *   std::cout << " Filling in the "
722 *   << count_depressions(elevation_data)
723 *   << " depressions in the elevation model" << std::endl;
724 *   fill_depressions(elevation_data);
725 *  
726 * @endcode
727 *
728 * Check that we have no depressions left:
729 *
730 * @code
731 *   Assert(count_depressions(elevation_data) == 0, ExcInternalError());
732 *  
733 * @endcode
734 *
735 * Write the elevation data to the cache file using binary
736 * serialization
737 *
738 * @code
739 *   std::cout << " Writing " << elevation_data.size()[0] << " x "
740 *   << elevation_data.size()[1]
741 *   << " elevation points to cache file " << cache_filename
742 *   << std::endl;
743 *   boost::iostreams::filtering_ostream out;
744 *   out.push(boost::iostreams::basic_gzip_compressor<>());
745 *   out.push(boost::iostreams::file_sink(cache_filename));
746 *   boost::archive::binary_oarchive oa(out);
747 *   oa << n_longitudes << n_latitudes << lower_left_corner << pixel_size
748 *   << elevation_data;
749 *   }
750 *   else
751 *   {
752 * @endcode
753 *
754 * If we did find a cache file read by a previous run of the
755 * program,
756 * read the elevation data using binary deserialization
757 *
758 * @code
759 *   std::cout << " Reading elevation data from cache file "
760 *   << cache_filename << std::endl;
761 *  
762 *   boost::iostreams::filtering_istream in;
763 *   in.push(boost::iostreams::basic_gzip_decompressor<>());
764 *   in.push(boost::iostreams::file_source(cache_filename));
765 *   boost::archive::binary_iarchive ia(in);
766 *   ia >> n_longitudes >> n_latitudes >> lower_left_corner >>
767 *   pixel_size >> elevation_data;
768 *  
769 *   std::cout << " Read " << elevation_data.size()[0] << " x "
770 *   << elevation_data.size()[1]
771 *   << " elevation points from cache file" << std::endl;
772 *   }
773 *   }
774 *  
775 * @endcode
776 *
777 * Finally, distribute the data created on process 0 to everyone else and
778 * create a function object that can be used to evaluate the elevation at
779 * arbitrary points.
780 *
781 * @code
782 *   n_latitudes =
783 *   Utilities::MPI::broadcast(mpi_communicator, n_latitudes, root_process);
784 *   n_longitudes =
785 *   Utilities::MPI::broadcast(mpi_communicator, n_longitudes, root_process);
786 *   lower_left_corner = Utilities::MPI::broadcast(mpi_communicator,
787 *   lower_left_corner,
788 *   root_process);
789 *   pixel_size =
790 *   Utilities::MPI::broadcast(mpi_communicator, pixel_size, root_process);
791 *  
792 *   elevation_data.replicate_across_communicator(mpi_communicator,
793 *   root_process);
794 *  
795 *   data = std::make_unique<const Functions::InterpolatedUniformGridData<2>>(
796 *   std::array<std::pair<double, double>, 2>{
797 *   {std::make_pair(lower_left_corner[0],
798 *   lower_left_corner[0] + (n_longitudes - 1) * pixel_size),
799 *   std::make_pair(lower_left_corner[1],
800 *   lower_left_corner[1] +
801 *   (n_latitudes - 1) * pixel_size)}},
802 *   std::array<unsigned int, 2>{{n_longitudes - 1, n_latitudes - 1}},
803 *   std::move(elevation_data));
804 *   }
805 *  
806 *  
807 * @endcode
808 *
809 *
810 * <a name="parallel_flow_routing.cc-ColoradoTopographyvalue"></a>
811 * <h3>ColoradoTopography::value()</h3>
812 *
813
814 *
815 * This function evaluates the elevation at a given 3D point. Since the mesh
816 * is embedded in 3D on the surface of the Earth (as a sphere of radius 6371
817 * km), the input point p is given in Cartesian coordinates. We convert these
818 * to geographic coordinates (longitude and latitude in degrees) and then look
819 * up the elevation in the stored data table using bilinear interpolation.
820 *
821
822 *
823 * The conversion from Cartesian to geographic coordinates uses standard
824 * formulas:
825 * - Longitude (x-y plane angle): atan2(y, x) * 360 / (2π)
826 * - Latitude (z-radial angle): atan2(z, sqrt(x² + y²)) * 360 / (2π)
827 *
828 * @code
829 *   double
830 *   ColoradoTopography::value(const Point<3> &p,
831 *   const unsigned int /*component*/) const
832 *   {
833 * @endcode
834 *
835 * First pull back p to longitude/latitude, expressed in degrees
836 *
837 * @code
838 *   const Point<2> p_long_lat(std::atan2(p[1], p[0]) * 360 / (2 * numbers::PI),
839 *  
840 *   std::atan2(p[2],
841 *   std::sqrt(p[0] * p[0] + p[1] * p[1])) *
842 *   360 / (2 * numbers::PI));
843 *  
844 *   return data->value(p_long_lat);
845 *   }
846 *  
847 *  
848 * @endcode
849 *
850 *
851 * <a name="parallel_flow_routing.cc-ColoradoTopographycount_depressions"></a>
852 * <h3>ColoradoTopography::count_depressions()</h3>
853 *
854
855 *
856 * This static helper function counts the number of local depressions in an
857 * elevation table. A local depression is a grid point in the interior of the
858 * domain that is lower than all of its 8 neighbors (i.e., all
859 * immediate neighbors including diagonals).
860 *
861
862 *
863 * We only check interior points (excluding the boundary of the domain)
864 * because boundary points can have lower neighbors (water can flow out of the
865 * domain at the boundary at these points).
866 *
867 * @code
868 *   unsigned int
869 *   ColoradoTopography::count_depressions(const Table<2, double> &elevation_data)
870 *   {
871 *   const unsigned int n_longitudes = elevation_data.size()[0];
872 *   const unsigned int n_latitudes = elevation_data.size()[1];
873 *  
874 *   unsigned int n_depressions = 0;
875 *   for (unsigned int x = 1; x < n_longitudes - 1; ++x)
876 *   for (unsigned int y = 1; y < n_latitudes - 1; ++y)
877 *   {
878 *   const double elevation = elevation_data(x, y);
879 *   double min_neighbor_elevation = std::numeric_limits<double>::max();
880 *   for (int i = -1; i <= +1; ++i)
881 *   for (int j = -1; j <= +1; ++j)
882 *   if (!(i == 0 && j == 0))
883 *   min_neighbor_elevation = std::min(min_neighbor_elevation,
884 *   elevation_data(x + i, y + j));
885 *   if (min_neighbor_elevation >= elevation)
886 *   ++n_depressions;
887 *   }
888 *   return n_depressions;
889 *   }
890 *  
891 *  
892 * @endcode
893 *
894 *
895 * <a name="parallel_flow_routing.cc-ColoradoTopographyfill_depressions"></a>
896 * <h3>ColoradoTopography::fill_depressions()</h3>
897 *
898
899 *
900 * This function removes local depressions (sinks) from a gridded elevation
901 * model using a priority-flood algorithm. In topographic data, depressions
902 * are local minima that are lower than all their neighbors; they are
903 * problematic for flow routing because they trap water that would
904 * otherwise flow downhill.
905 *
906
907 *
908 * The key idea is to work "inward" from the boundaries: we start by marking
909 * all border cells as processed and placing them in a priority queue sorted
910 * by elevation (lowest first). Then, we repeatedly extract the lowest cell
911 * from the queue and examine its unprocessed 8-connected neighbors
912 * (including diagonals). For each unprocessed neighbor, we compute its
913 * filled elevation as the maximum of its current elevation and the parent
914 * cell's elevation. To avoid creating perfectly flat plateaus that would
915 * be ambiguous for flow routing, we add a small deterministic increment
916 * (derived from the cell's grid indices). We then mark the neighbor as
917 * processed, update its elevation in-place, and add it to the queue. This
918 * continues until the queue is empty.
919 *
920
921 *
922 * The result is that no interior cell is lower than all of its neighbors.
923 * The algorithm runs in O(N log N) time where N is the number of grid cells,
924 * and the deterministic increment ensures reproducibility across runs.
925 *
926
927 *
928 * @note In hindsight, the choice of a random increment between zero and
929 * 0.1 may have been a bit large -- we really just want to avoid flat
930 * plateaus, so an increment on the order of 0.01 or even smaller would have
931 * been sufficient. The current choice may create some small artificial
932 * slopes that might add up to too much elevation change across the domain.
933 * But, this is the value used for the experiments in the accompanying paper,
934 * so we keep it as is for now.
935 *
936 * @code
937 *   void
938 *   ColoradoTopography::fill_depressions(Table<2, double> &elevation_data)
939 *   {
940 *   struct Node
941 *   {
942 *   unsigned int x, y;
943 *   double elev;
944 *   bool
945 *   operator>(const Node &other) const
946 *   {
947 *   return elev > other.elev;
948 *   }
949 *   };
950 *  
951 *   const unsigned int n_rows = elevation_data.size()[0];
952 *   const unsigned int n_cols = elevation_data.size()[1];
953 *   Table<2, bool> processed(n_rows, n_cols);
954 *   processed.fill(false);
955 *  
956 * @endcode
957 *
958 * A priority queue that always gives us the lowest elevation node that
959 * has not yet been processed. std::priority_queue is a rarely used
960 * data structure that is a heap-based implementation of a priority queue.
961 * You can find its description at
962 * https://en.cppreference.com/w/cpp/container/priority_queue.html
963 *
964 * @code
965 *   std::priority_queue<Node,
966 *   std::vector<Node>,
967 *   /* sort low-to-high */ std::greater<Node>>
968 *   currently_active_nodes;
969 *  
970 *   std::mt19937 rng;
971 *  
972 * @endcode
973 *
974 * Push all border nodes into the priority queue
975 *
976 * @code
977 *   for (unsigned int i = 0; i < n_rows; ++i)
978 *   {
979 *   currently_active_nodes.push(Node{i, 0, elevation_data[i][0]});
980 *   currently_active_nodes.push(
981 *   Node{i, n_cols - 1, elevation_data[i][n_cols - 1]});
982 *   processed[i][0] = processed[i][n_cols - 1] = true;
983 *   }
984 *   for (unsigned int j = 0; j < n_cols; ++j)
985 *   {
986 *   currently_active_nodes.push(Node{0, j, elevation_data[0][j]});
987 *   currently_active_nodes.push(
988 *   Node{n_rows - 1, j, elevation_data[n_rows - 1][j]});
989 *   processed[0][j] = processed[n_rows - 1][j] = true;
990 *   }
991 *  
992 * @endcode
993 *
994 * Directions for 8 neighbors
995 *
996 * @code
997 *   const int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1};
998 *   const int dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
999 *  
1000 * @endcode
1001 *
1002 * While there are nodes for which we can still look for neighbors:
1003 *
1004 * @code
1005 *   while (!currently_active_nodes.empty())
1006 *   {
1007 *   const Node current_node =
1008 *   currently_active_nodes
1009 *   .top(); // get the lowest point currently in the queue
1010 *   currently_active_nodes.pop(); // and then remove it from the queue
1011 *  
1012 * @endcode
1013 *
1014 * Loop over the neighbors of the lowest point in the queue, excluding
1015 * points that are beyond the boundary of the domain and also skipping
1016 * over the ones that have already been processed:
1017 *
1018 * @code
1019 *   for (unsigned int k = 0; k < 8; ++k)
1020 *   if ((static_cast<signed int>(current_node.x) + dx[k] >= 0) &&
1021 *   (static_cast<signed int>(current_node.x) + dx[k] <
1022 *   static_cast<signed int>(n_rows)) &&
1023 *   (static_cast<signed int>(current_node.y) + dy[k] >= 0) &&
1024 *   (static_cast<signed int>(current_node.y) + dy[k] <
1025 *   static_cast<signed int>(n_cols)))
1026 *  
1027 *   {
1028 *   const unsigned int neighbor_x = current_node.x + dx[k];
1029 *   const unsigned int neighbor_y = current_node.y + dy[k];
1030 *  
1031 *   if (processed[neighbor_x][neighbor_y])
1032 *   continue;
1033 *  
1034 * @endcode
1035 *
1036 * If the neighbor exists and has not been processed:
1037 * Add a random increment between 0 and 0.1 meters to ensure
1038 * that we do not create perfectly flat plateaus. The increment is
1039 * random but deterministic to ensure reproducibility across runs.
1040 *
1041 * @code
1042 *   const double new_elevation =
1043 *   std::max(elevation_data[neighbor_x][neighbor_y],
1044 *   current_node.elev +
1045 *   std::uniform_real_distribution<>(0, 0.1)(rng));
1046 *   elevation_data[neighbor_x][neighbor_y] = new_elevation;
1047 *   processed[neighbor_x][neighbor_y] = true;
1048 *  
1049 * @endcode
1050 *
1051 * Push that neighbor to the queue:
1052 *
1053 * @code
1054 *   currently_active_nodes.push(
1055 *   {static_cast<unsigned int>(neighbor_x),
1056 *   static_cast<unsigned int>(neighbor_y),
1057 *   new_elevation});
1058 *   }
1059 *   }
1060 *   }
1061 *  
1062 *  
1063 *  
1064 * @endcode
1065 *
1066 *
1067 * <a name="parallel_flow_routing.cc-RainFallRate"></a>
1068 * <h3>RainFallRate</h3>
1069 *
1070
1071 *
1072 * This is a simple class that describes the rain fall rate on the domain.
1073 * In reality, the rain fall rate varies depending on location and climate
1074 * conditions, but for this program we use a constant value everywhere on
1075 * the domain. The rain fall rate is an important boundary condition for
1076 * the flow routing problem: it represents the water that enters the system
1077 * through precipitation, and eventually either flows out through the
1078 * boundary or accumulates in local depressions.
1079 *
1080
1081 *
1082 * The value of 375 mm per year (approximately 15 inches per year) is
1083 * roughly representative of the rainfall in Colorado.
1084 *
1085 * @code
1086 *   template <int spacedim>
1087 *   class RainFallRate : public Function<spacedim>
1088 *   {
1089 *   public:
1090 *   virtual double
1091 *   value(const Point<spacedim> &p,
1092 *   const unsigned int component = 0) const override;
1093 *   };
1094 *  
1095 *  
1096 *   template <int spacedim>
1097 *   double
1098 *   RainFallRate<spacedim>::value(const Point<spacedim> & /*p*/,
1099 *   const unsigned int /*component*/) const
1100 *   {
1101 *   return 0.375;
1102 *   }
1103 *  
1104 *  
1105 *  
1106 *  
1144 *   class ParallelFlowRouter : public ParameterAcceptor
1145 *   {
1146 *   public:
1147 *   static constexpr int dim = 2;
1148 *   static constexpr int spacedim = 3;
1149 *  
1150 *   ParallelFlowRouter();
1151 *  
1152 *   void
1153 *   run();
1154 *  
1155 *   private:
1156 *   void
1157 *   make_grid();
1158 *  
1159 *   void
1160 *   setup_dofs();
1161 *  
1162 *   void
1163 *   interpolate_initial_elevation();
1164 *  
1165 *   void
1166 *   sort_dofs_high_to_low();
1167 *  
1168 *   void
1169 *   compute_local_flow_routing();
1170 *  
1171 *   void
1172 *   assemble_system();
1173 *  
1174 *   void
1175 *   assemble_matrix_free_operators();
1176 *  
1177 *   void
1178 *   solve();
1179 *  
1180 *   void
1181 *   check_conservation_for_waterflow_system(const VectorType &solution);
1182 *  
1183 *   void
1184 *   output_results();
1185 *  
1186 *   const MPI_Comm mpi_communicator;
1187 *  
1188 *   unsigned int n_refinements;
1189 *   bool generate_graphical_output;
1190 *  
1191 *   const FESystem<dim, spacedim> fe;
1193 *   DoFHandler<dim, spacedim> dof_handler;
1194 *  
1195 *   IndexSet locally_relevant_dofs;
1196 *   std::vector<IndexSet> locally_owned_partitioning;
1197 *   std::vector<IndexSet> locally_relevant_partitioning;
1198 *  
1199 *   IndexSet locally_relevant_water_dofs;
1200 *  
1201 *   std::vector<std::pair<types::global_dof_index, types::global_dof_index>>
1202 *   local_flow_routing;
1203 *  
1204 *   MatrixType system_matrix;
1205 *   VectorType locally_relevant_solution;
1206 *   VectorType locally_relevant_solution_dot;
1207 *   VectorType system_rhs;
1208 *  
1209 *   class FlowRoutingMatrix;
1210 *   std::unique_ptr<const FlowRoutingMatrix> flow_routing_matrix;
1211 *  
1212 *   class FlowRoutingPreconditioner;
1213 *   std::unique_ptr<const FlowRoutingPreconditioner>
1214 *   flow_routing_preconditioner;
1215 *  
1216 *   class IplusminusXMatrixBase;
1217 *  
1218 *   class IplusXMatrix;
1219 *   std::unique_ptr<const IplusXMatrix> I_plus_X_matrix;
1220 *  
1221 *   class IminusXMatrix;
1222 *   std::unique_ptr<const IminusXMatrix> I_minus_X_matrix;
1223 *  
1224 *   ConditionalOStream pcout;
1225 *   TimerOutput computing_timer;
1226 *   };
1227 *  
1228 *  
1229 *   ParallelFlowRouter::ParallelFlowRouter()
1230 *   : ParameterAcceptor("ParallelFlowRouter")
1231 *   , mpi_communicator(MPI_COMM_WORLD)
1232 *   , n_refinements(9)
1233 *   , fe(FE_Q<dim, spacedim>(1) ^ 2)
1234 *   , triangulation(mpi_communicator,
1238 *   , dof_handler(triangulation)
1239 *   , pcout(std::cout,
1240 *   (Utilities::MPI::this_mpi_process(mpi_communicator) == 0))
1241 *   , computing_timer(mpi_communicator,
1242 *   pcout,
1245 *   {
1246 *   add_parameter("Number of refinements",
1247 *   n_refinements,
1248 *   "The number of global refinements to perform.");
1249 *   add_parameter("Generate graphical output",
1250 *   generate_graphical_output,
1251 *   "Whether to generate graphical output files.");
1252 *   }
1253 *  
1254 *  
1255 * @endcode
1256 *
1257 *
1258 * <a name="parallel_flow_routing.cc-ParallelFlowRoutermake_grid"></a>
1259 * <h3>ParallelFlowRouter::make_grid()</h3>
1260 *
1261
1262 *
1263 * This function creates the mesh that discretizes the Colorado topography
1264 * domain. Rather than using a flat Cartesian coordinate system, the mesh is
1265 * mapped onto the surface of the Earth (a sphere of radius 6371 km), so that
1266 * distances and areas computed on the mesh are realistic.
1267 *
1268
1269 *
1270 * The function starts by creating a rectangular mesh that spans Colorado's
1271 * geographic extent (7 degrees longitude × 4 degrees latitude). After
1272 * global refinements, this mesh is then transformed from longitude-latitude
1273 * coordinates to 3D Cartesian coordinates on the Earth's surface via a
1274 * cylindrical projection. This ensures that when we compute gradients or
1275 * areas on the mesh, they respect the curvature of the Earth, which is
1276 * essential for correct flow routing on real terrain.
1277 *
1278 * @code
1279 *   void
1280 *   ParallelFlowRouter::make_grid()
1281 *   {
1282 *   TimerOutput::Scope t(computing_timer, "Make grid");
1283 *   pcout << "Making grid... " << std::endl;
1284 *  
1286 *   {7,
1287 *   4}, // Colorado spans 7x4 degrees
1288 *   Point<2>(-109., 37.),
1289 *   Point<2>(-102., 41.));
1290 *  
1291 *   triangulation.refine_global(n_refinements);
1292 *  
1294 *   [](const Point<spacedim> &p_long_lat_degrees) {
1295 *   const Point<2> p_long_lat(p_long_lat_degrees[0] / 360 *
1296 *   (2 * numbers::PI),
1297 *   p_long_lat_degrees[1] / 360 *
1298 *   (2 * numbers::PI));
1299 *   const double R = 6371000;
1300 *   return Point<spacedim>(R * std::cos(p_long_lat[1]) *
1301 *   std::cos(p_long_lat[0]), // X
1302 *   R * std::cos(p_long_lat[1]) *
1303 *   std::sin(p_long_lat[0]), // Y
1304 *   R * std::sin(p_long_lat[1])); // Z
1305 *   },
1306 *   triangulation);
1307 *  
1308 *   pcout << " Number of cells: " << triangulation.n_global_active_cells()
1309 *   << std::endl;
1310 *  
1311 *   double area = 0;
1312 *   for (const auto &cell : triangulation.active_cell_iterators())
1313 *   area += cell->measure();
1314 *   pcout << " Area of the domain: " << area << "m^2" << std::endl;
1315 *   }
1316 *  
1317 *  
1318 * @endcode
1319 *
1320 *
1321 * <a name="parallel_flow_routing.cc-ParallelFlowRoutersetup_system"></a>
1322 * <h3>ParallelFlowRouter::setup_system()</h3>
1323 *
1324
1325 *
1326 * The next function also is not something that is new in any particular
1327 * way. Conceptually, all we have to do is set up block vectors and matrices
1328 * for the linear systems we want to solve. This really is quite
1329 * straightforward with the only complication that we have to account for
1330 * the fact that we are working in a parallel program where we have to
1331 * keep track which process owns which degrees of freedom.
1332 *
1333
1334 *
1335 * This function does the basic set-up. In following functions, we will
1336 * re-enumerate the degrees of freedom in a way that makes the flow routing
1337 * matrix have a nice triangular structure, and we will also set up the
1338 * matrix-free operators that we will use to compare the matrix-based and
1339 * matrix-free solvers.
1340 *
1341 * @code
1342 *   void
1343 *   ParallelFlowRouter::setup_dofs()
1344 *   {
1345 *   TimerOutput::Scope t(computing_timer, "Setup system");
1346 *   pcout << "Setting up system... " << std::endl;
1347 *  
1348 *   dof_handler.distribute_dofs(fe);
1349 *   DoFRenumbering::component_wise(dof_handler);
1350 *  
1351 *   const std::vector<types::global_dof_index> dofs_per_block =
1353 *  
1354 *   const types::global_dof_index n_elevation_dofs = dofs_per_block[0];
1355 *   const types::global_dof_index n_waterflow_rate_dofs = dofs_per_block[1];
1356 *  
1357 *   {
1358 *   const IndexSet &locally_owned_dofs = dof_handler.locally_owned_dofs();
1359 *   locally_owned_partitioning = {
1360 *   locally_owned_dofs.get_view(0, n_elevation_dofs),
1361 *   locally_owned_dofs.get_view(n_elevation_dofs,
1362 *   n_elevation_dofs + n_waterflow_rate_dofs)};
1363 *   }
1364 *  
1365 *  
1366 *   locally_relevant_dofs =
1368 *   locally_relevant_partitioning = {
1369 *   locally_relevant_dofs.get_view(0, n_elevation_dofs),
1370 *   locally_relevant_dofs.get_view(n_elevation_dofs,
1371 *   n_elevation_dofs + n_waterflow_rate_dofs)};
1372 *  
1373 *   IndexSet all_elevation_dofs(dof_handler.n_dofs());
1374 *   all_elevation_dofs.add_range(0, n_elevation_dofs);
1375 *   locally_relevant_water_dofs = locally_relevant_dofs;
1376 *   locally_relevant_water_dofs.subtract_set(all_elevation_dofs);
1377 *  
1378 *   locally_relevant_solution.reinit(locally_owned_partitioning,
1379 *   locally_relevant_partitioning,
1380 *   mpi_communicator);
1381 *   locally_relevant_solution_dot.reinit(locally_owned_partitioning,
1382 *   locally_relevant_partitioning,
1383 *   mpi_communicator);
1384 *   system_rhs.reinit(locally_owned_partitioning, mpi_communicator);
1385 *  
1386 *   pcout << " Number of degrees of freedom: " << dof_handler.n_dofs()
1387 *   << " (elevation: " << n_elevation_dofs
1388 *   << ", waterflow: " << n_waterflow_rate_dofs << ')' << std::endl;
1389 *   const std::vector<types::global_dof_index> water_dofs_per_process =
1390 *   Utilities::MPI::all_gather(mpi_communicator,
1391 *   locally_owned_partitioning[1].n_elements());
1392 *   pcout << " Number of waterflow degrees of freedom per process: "
1393 *   << std::accumulate(water_dofs_per_process.begin(),
1394 *   water_dofs_per_process.end(),
1395 *   0) /
1396 *   Utilities::MPI::n_mpi_processes(mpi_communicator)
1397 *   << " (average) x "
1398 *   << Utilities::MPI::n_mpi_processes(mpi_communicator)
1399 *   << " (number of processes)" << std::endl;
1400 *   }
1401 *  
1402 *  
1403 * @endcode
1404 *
1405 *
1406 * <a name="parallel_flow_routing.cc-ParallelFlowRouterinterpolate_initial_elevation"></a>
1407 * <h3>ParallelFlowRouter::interpolate_initial_elevation()</h3>
1408 *
1409
1410 *
1411 * The following function then interpolates the initial elevation onto the
1412 * mesh. We need this as initial conditions for the elevation variable.
1413 *
1414
1415 *
1416 * The way this function works is that given a scalar function object (derived
1417 * from the Function class), we first create a function object that covers
1418 * all solution variables, returns the elevation in one vector component
1419 * (specifically, in vector component zero) and zeros in all others. This
1420 * "extension" of a scalar to a vector function is done by the
1421 * VectorFunctionFromScalarFunctionObject class. We can then use this
1422 * extended function object to interpolate these initial conditions onto
1423 * all degrees of freedom, which correctly sets the initial elevation
1424 * variables to their initial values and sets the water flow rate to
1425 * zero.
1426 *
1427 * @code
1428 *   void
1429 *   ParallelFlowRouter::interpolate_initial_elevation()
1430 *   {
1431 *   TimerOutput::Scope t(computing_timer,
1432 *   "Initial conditions: interpolate elevation");
1433 *   pcout << "Interpolating elevation... " << std::endl;
1434 *  
1435 *   const ColoradoTopography colorado_topography(mpi_communicator,
1436 *   n_refinements);
1438 *   [&](const Point<spacedim> &p) { return colorado_topography.value(p); },
1439 *   /* elevation vector component = */ 0,
1440 *   /* total number of vector components = */ 2);
1441 *  
1442 *   VectorType interpolated_initial_condition(locally_owned_partitioning,
1443 *   MPI_COMM_WORLD);
1444 *   VectorTools::interpolate(dof_handler,
1445 *   initial_values,
1446 *   interpolated_initial_condition);
1447 *  
1448 * @endcode
1449 *
1450 * The vector we have just interpolated into is a "fully distributed
1451 * vector", i.e., every element is uniquely owned by one of the MPI
1452 * processes and these are the only ones we store on the current process.
1453 * On the other hand, we will also have to access values for nodes that
1454 * are owned by other processes (for example on ghost cells), so we copy
1455 * the vector into one that also has these ghost entries:
1456 *
1457 * @code
1458 *   locally_relevant_solution = interpolated_initial_condition;
1459 *   }
1460 *  
1461 *  
1462 *  
1463 * @endcode
1464 *
1465 *
1466 * <a name="parallel_flow_routing.cc-ParallelFlowRoutersort_dofs_high_to_low"></a>
1467 * <h3>ParallelFlowRouter::sort_dofs_high_to_low()</h3>
1468 *
1469
1470 *
1471 * The key insight for efficiently solving the flow routing problem is that
1472 * water always flows downhill. This means we can process water flow
1473 * calculations in order from the highest elevation to the lowest. This
1474 * ordering is crucial for the performance of the solver: it means that when
1475 * we compute the water flow at a given node, all of the upstream nodes from
1476 * which it receives water have already been processed. This is the
1477 * characteristic of a triangular system, and it allows us to solve the
1478 * system very efficiently using a simple substitution method.
1479 *
1480
1481 *
1482 * This function implements this idea by renumbering the degrees of freedom
1483 * that represent the water flow rate such that they are ordered from highest
1484 * elevation to lowest. This doesn't change the mathematical problem we're
1485 * solving, but it transforms the matrix of the linear system into a lower
1486 * triangular matrix when water flows are processed in this order, making
1487 * the solver much more efficient.
1488 *
1489
1490 *
1491 * The algorithm works by first collecting all water degrees of freedom and
1492 * their corresponding elevations, sorting them from highest to lowest, and
1493 * then renumbering the DoFs accordingly. After renumbering, we update the
1494 * index sets used for parallel communication to reflect the new ordering.
1495 *
1496
1497 *
1498 * As the paper notes, this reordering step is not actually necessary if
1499 * you implement the per-process high-to-low flow routing algorithm in a
1500 * matrix-free way, but it is convenient for the matrix-based solver because
1501 * in that case, the preconditioner is a triangular solve which is exactly
1502 * what the Gauss-Seidel (SOR) method will perform and so we can use what
1503 * PETSc offers to us without having to implement a custom preconditioner.
1504 *
1505 * @code
1506 *   void
1507 *   ParallelFlowRouter::sort_dofs_high_to_low()
1508 *   {
1509 *   TimerOutput::Scope t(computing_timer,
1510 *   "Initial conditions: sort DoFs high to low");
1511 *   pcout << "Sorting DoFs high to low... " << std::endl;
1512 *  
1513 *   std::map<types::global_dof_index, double> water_dof_index_to_elevation_map;
1514 *   for (const auto &cell : dof_handler.active_cell_iterators())
1515 *   if (cell->is_locally_owned())
1516 *   for (unsigned int v = 0; v < cell->reference_cell().n_vertices(); ++v)
1517 *   {
1518 *   const types::global_dof_index vertex_water_dof =
1519 *   cell->vertex_dof_index(v, 1);
1520 *  
1521 *   if (dof_handler.locally_owned_dofs().is_element(vertex_water_dof))
1522 *   {
1523 *   const types::global_dof_index vertex_elevation_dof =
1524 *   cell->vertex_dof_index(v, 0);
1525 *   const double vertex_elevation =
1526 *   locally_relevant_solution(vertex_elevation_dof);
1527 *   water_dof_index_to_elevation_map[vertex_water_dof] =
1528 *   vertex_elevation;
1529 *   }
1530 *   }
1531 *  
1532 *   std::vector<std::pair<types::global_dof_index, double>>
1533 *   water_dof_index_to_elevation_list(
1534 *   water_dof_index_to_elevation_map.size());
1535 *   std::copy(water_dof_index_to_elevation_map.begin(),
1536 *   water_dof_index_to_elevation_map.end(),
1537 *   water_dof_index_to_elevation_list.begin());
1538 *   std::sort(water_dof_index_to_elevation_list.begin(),
1539 *   water_dof_index_to_elevation_list.end(),
1540 *   [](const std::pair<types::global_dof_index, double> &a,
1541 *   const std::pair<types::global_dof_index, double> &b) {
1542 *   return a.second > b.second;
1543 *   });
1544 *  
1545 * @endcode
1546 *
1547 * At this point, we have a sorted list of water dof indices, sorted high to
1548 * low based on their elevation. We want to renumber the existing water
1549 * DoF indices so that they follow this ordering. We will do so
1550 * by making use of the fact that locally owned DoFs are numbered in
1551 * a contiguous block, so we can start numbering all locally owned
1552 * water DoFs at the first water DoF index.
1553 *
1554 * @code
1555 *   Assert(locally_owned_partitioning[0].is_contiguous(), ExcInternalError());
1556 *   Assert(locally_owned_partitioning[1].is_contiguous(), ExcInternalError());
1557 *   AssertDimension(locally_owned_partitioning[1].n_elements(),
1558 *   water_dof_index_to_elevation_list.size());
1559 *   std::map<types::global_dof_index, types::global_dof_index>
1560 *   old_to_new_water_indices;
1561 *   if (water_dof_index_to_elevation_list.size() >
1562 *   0) // make sure this also works if a processor has no DoFs
1563 *   {
1564 *   const types::global_dof_index first_water_dof_index =
1565 *   locally_owned_partitioning[0].size() +
1566 *   *locally_owned_partitioning[1].begin();
1567 *   for (unsigned int i = 0; i < water_dof_index_to_elevation_list.size();
1568 *   ++i)
1569 *   old_to_new_water_indices[water_dof_index_to_elevation_list[i].first] =
1570 *   first_water_dof_index + i;
1571 *   }
1572 *  
1573 * @endcode
1574 *
1575 * Now we can do the renumbering:
1576 *
1577 * @code
1578 *   std::vector<types::global_dof_index> new_dof_numbers;
1579 *   new_dof_numbers.reserve(dof_handler.n_locally_owned_dofs());
1580 *  
1581 * @endcode
1582 *
1583 * Do not re-enumerate the elevation DoFs at all.
1584 *
1585 * @code
1586 *   for (const types::global_dof_index elevation_dof :
1587 *   locally_owned_partitioning[0])
1588 *   new_dof_numbers.push_back(elevation_dof);
1589 *  
1590 *   for (const types::global_dof_index dof_index :
1591 *   dof_handler.locally_owned_dofs())
1592 *   if (dof_index >= dof_handler.n_dofs() / 2)
1593 *   {
1594 *   Assert(old_to_new_water_indices.find(dof_index) !=
1595 *   old_to_new_water_indices.end(),
1596 *   ExcInternalError());
1597 *   new_dof_numbers.push_back(old_to_new_water_indices[dof_index]);
1598 *   }
1599 *   AssertDimension(new_dof_numbers.size(), dof_handler.n_locally_owned_dofs());
1600 *  
1601 *   dof_handler.renumber_dofs(new_dof_numbers);
1602 *  
1603 * @endcode
1604 *
1605 * Rebuild index sets after renumbering
1606 *
1607 * @code
1608 *   locally_relevant_dofs =
1610 *   const std::vector<types::global_dof_index> dofs_per_block =
1612 *   const types::global_dof_index n_elevation_dofs = dofs_per_block[0];
1613 *   IndexSet all_elevation_dofs(dof_handler.n_dofs());
1614 *   all_elevation_dofs.add_range(0, n_elevation_dofs);
1615 *   locally_relevant_water_dofs = locally_relevant_dofs;
1616 *   locally_relevant_water_dofs.subtract_set(all_elevation_dofs);
1617 *   locally_relevant_partitioning = {
1618 *   locally_relevant_dofs.get_view(0, n_elevation_dofs),
1619 *   locally_relevant_dofs.get_view(n_elevation_dofs, dof_handler.n_dofs())};
1620 *  
1621 *   pcout
1622 *   << " Elevations range between "
1623 *   << Utilities::MPI::min(water_dof_index_to_elevation_list.size() > 0 ?
1624 *   water_dof_index_to_elevation_list.back().second :
1625 *   std::numeric_limits<double>::max(),
1626 *   mpi_communicator)
1627 *   << "m and "
1628 *   << Utilities::MPI::max(
1629 *   water_dof_index_to_elevation_list.size() > 0 ?
1630 *   water_dof_index_to_elevation_list.front().second :
1631 *   -std::numeric_limits<double>::max(),
1632 *   mpi_communicator)
1633 *   << "m." << std::endl;
1634 *   }
1635 *  
1636 *  
1637 * @endcode
1638 *
1639 *
1640 * <a name="parallel_flow_routing.cc-ParallelFlowRoutercompute_local_flow_routing"></a>
1641 * <h3>ParallelFlowRouter::compute_local_flow_routing()</h3>
1642 *
1643
1644 *
1645 * Next, we need to have a function that for each degree of freedom (locally
1646 * owned or ghost) finds which downhill neighbor (if any) it gives water to.
1647 * This corresponds to the D8 scheme used in many flow routing codes, where
1648 * each node gives water to exactly one other node -- specifically, to the
1649 * neighbor in the direction of steepest descent. Because we consider the
1650 * four immediate neighbors of a node on a regular mesh plus the four
1651 * diagonal neighbors (8 neighbors total, hence "D8"), the neighbor with the
1652 * steepest downhill slope may not necessarily be the lowest-lying neighbor.
1653 *
1654
1655 *
1656 * The algorithm works by examining all locally relevant cells and their
1657 * vertices. For each vertex, we compute the slope to all other vertices in
1658 * the same cell. If the slope is negative (i.e., downhill), we check if it's
1659 * steeper than the previously found downhill direction, and if so, we record
1660 * it as the new destination for water from this vertex.
1661 *
1662
1663 *
1664 * Since we're working in parallel, we only have complete information about
1665 * the neighbors of locally owned vertices. For vertices on the boundaries
1666 * of the parallel partition (but still locally relevant), we need to exchange
1667 * information with neighboring processes via ghost cells. This is done using
1669 *
1670
1671 *
1672 * The final result is stored in the local_flow_routing member variable,
1673 * which maps each locally relevant water degree of freedom to its downstream
1674 * neighbor (or to numbers::invalid_dof_index if the vertex is at a boundary
1675 * or in a depression with no outlet).
1676 *
1677 * @code
1678 *   void
1679 *   ParallelFlowRouter::compute_local_flow_routing()
1680 *   {
1681 *   TimerOutput::Scope t(computing_timer, "Compute local routing");
1682 *   pcout << "Computing local routing... " << std::endl;
1683 *  
1684 * @endcode
1685 *
1686 * First, create a map from each DoF to a pair (index,slope) of neighbors.
1687 * These slopes must necessarily be negative because water only flows
1688 * downhill.
1689 *
1690
1691 *
1692 * We initialize this map by looping over all locally relevant water DoFs
1693 * and setting the value of the map to (index=invalid, slope=0).
1694 *
1695 * @code
1696 *   std::map<types::global_dof_index,
1697 *   std::pair<types::global_dof_index, double>>
1698 *   water_dofs_to_steepest_downhill_neighbor_and_slope;
1699 *   for (const types::global_dof_index i : locally_relevant_water_dofs)
1700 *   water_dofs_to_steepest_downhill_neighbor_and_slope[i] = {
1702 *  
1703 * @endcode
1704 *
1705 * Then loop over all locally owned and ghost cells, get the locations
1706 * and elevations of the four vertices of each cell, along with the
1707 * indices of the water DoF.
1708 *
1709 * @code
1710 *   for (const auto &cell : dof_handler.active_cell_iterators())
1711 *   if (cell->is_locally_owned() || cell->is_ghost())
1712 *   {
1713 *   AssertDimension(cell->reference_cell().n_vertices(), 4);
1714 *  
1715 *   std::array<types::global_dof_index, 4> vertex_water_dof_indices;
1716 *   std::array<Point<spacedim>, 4> vertex_locations;
1717 *   std::array<double, 4> vertex_elevations;
1718 *  
1719 *   for (const unsigned int v : cell->reference_cell().vertex_indices())
1720 *   {
1721 *   vertex_water_dof_indices[v] = cell->vertex_dof_index(v, 1);
1722 *   vertex_locations[v] = cell->vertex(v);
1723 *  
1724 *   const types::global_dof_index vertex_elevation_dof_index =
1725 *   cell->vertex_dof_index(v, 0);
1726 *   vertex_elevations[v] =
1727 *   locally_relevant_solution(vertex_elevation_dof_index);
1728 *   }
1729 *  
1730 * @endcode
1731 *
1732 * Next, determine the slope from each vertex to each of the other
1733 * vertices. If it is negative (i.e., downhill) and more negative than
1734 * the previously most downhill slope we have encountered for a DoF,
1735 * then use this as the direction in which this DoF will give water.
1736 * (Of course, it is possible that we encounter a steeper downhill
1737 * direction next on this cell, or on another cell; in that case,
1738 * we will simply overwrite what we determine here.)
1739 *
1740 * @code
1741 *   for (const unsigned int v : cell->reference_cell().vertex_indices())
1742 *   for (const unsigned int w : cell->reference_cell().vertex_indices())
1743 *   if (v != w)
1744 *   if (vertex_elevations[w] < vertex_elevations[v])
1745 *   {
1746 * @endcode
1747 *
1748 * Compute the slope between two vertices. The slope is the
1749 * elevation difference divided by the distance. Since the
1750 * mesh is mapped onto the surface of the Earth (a sphere),
1751 * the correct
1752 * distance between two points would be along the surface of
1753 * the Earth. This would correctly account for the Earth's
1754 * curvature and give us the true slope on the terrain. But
1755 * it's
1756 * also difficult to compute, so we simply use the
1757 * straight-line distance between points (through the
1758 * Earth), which is a good approximation for small
1759 * distances.
1760 *
1761 * @code
1762 *   const double slope =
1763 *   (vertex_elevations[w] - vertex_elevations[v]) /
1764 *   ((vertex_locations[v] - vertex_locations[w]).norm());
1765 *   Assert(slope < 0, ExcInternalError());
1766 *   if (slope <
1767 *   water_dofs_to_steepest_downhill_neighbor_and_slope
1768 *   [vertex_water_dof_indices[v]]
1769 *   .second)
1770 *   water_dofs_to_steepest_downhill_neighbor_and_slope
1771 *   [vertex_water_dof_indices[v]] = {
1772 *   vertex_water_dof_indices[w], slope};
1773 *   }
1774 *   }
1775 *  
1776 * @endcode
1777 *
1778 * At this point, we no longer care about slopes because we have considered
1779 * all neighbors of all nodes and no longer need to compare slopes between
1780 * nodes and neighbors. So reduce the map to a smaller one that only
1781 * contains for each DoF who it gives water to.
1782 *
1783
1784 *
1785 * Secondly, we have worked on all locally relevant DoFs up to this point.
1786 * For all locally active DoFs (locally owned plus the ones on the interface
1787 * to ghost cells), we have considered all neighboring cells and so we can
1788 * be certain that we have their downstream neighbors right. But for the
1789 * nodes on the far side of the ghost cells (adjacent to artificial cells),
1790 * we have not seen all neighbor nodes, and so might have gotten wrong who
1791 * they give water to. As a consequence, we exclude those DoFs from the
1792 * reduced list that are not locally owned and instead obtain their
1793 * information via a ghost exchange. (We could exclude only the ones that
1794 * are not locally *active*, but that doesn't buy us anything and the test
1795 * for locally owned is cheaper because that's a contiguous set.)
1796 *
1797 * @code
1798 *   std::map<types::global_dof_index, types::global_dof_index>
1799 *   water_dofs_to_steepest_downhill_neighbor;
1800 *   for (const auto &[source_index, dest_index_and_slope] :
1801 *   water_dofs_to_steepest_downhill_neighbor_and_slope)
1802 *   if (dof_handler.locally_owned_dofs().is_element(source_index))
1803 *   water_dofs_to_steepest_downhill_neighbor.insert(
1804 *   {source_index, dest_index_and_slope.first});
1805 *  
1806 *   AssertDimension(water_dofs_to_steepest_downhill_neighbor.size(),
1807 *   dof_handler.locally_owned_dofs().n_elements() / 2);
1808 *  
1809 *   using CellLocalData =
1810 *   std::map<types::global_dof_index, types::global_dof_index>;
1811 *  
1812 * @endcode
1813 *
1814 * Pack up the locally owned water dof index entries in the map
1815 * above for the current cell:
1816 *
1817 * @code
1818 *   const auto pack_function =
1819 *   [this, &water_dofs_to_steepest_downhill_neighbor](
1820 *   const typename DoFHandler<dim, spacedim>::active_cell_iterator &cell) {
1821 *   Assert(cell->is_locally_owned(), ExcInternalError());
1822 *  
1823 *   CellLocalData cell_local_data;
1824 *   for (const unsigned int v : cell->reference_cell().vertex_indices())
1825 *   {
1826 *   const types::global_dof_index vertex_water_dof_index =
1827 *   cell->vertex_dof_index(v, 1);
1828 *   if (dof_handler.locally_owned_dofs().is_element(
1829 *   vertex_water_dof_index))
1830 *   {
1831 *   Assert(water_dofs_to_steepest_downhill_neighbor.find(
1832 *   vertex_water_dof_index) !=
1833 *   water_dofs_to_steepest_downhill_neighbor.end(),
1834 *   ExcInternalError());
1835 *   cell_local_data.insert({vertex_water_dof_index,
1836 *   water_dofs_to_steepest_downhill_neighbor
1837 *   [vertex_water_dof_index]});
1838 *   }
1839 *   }
1840 *   return cell_local_data;
1841 *   };
1842 *  
1843 * @endcode
1844 *
1845 * Unpack what the other processes have sent for the current cell (which
1846 * is a ghost cell here). Because these were locally owned on the other
1847 * cell, they are necessarily not locally owned but locally relevant
1848 * here, and we assert that.
1849 *
1850
1851 *
1852 * We will ultimately only care about flow from one to another node if
1853 * at least one of them is locally active. We already know that the
1854 * source index is not locally active, so we discard entries that have
1855 * a destination that is not locally active either. (You'd think we
1856 * could have filtered this out in the pack_function above already, but
1857 * what we pack up on a cell may be sent to multiple processes that have
1858 * this cell as a ghost cell, and while a destination index may not be
1859 * locally relevant on one process, it may be on another.) We keep
1860 * the ones where the destination is not set, which indicates that
1861 * the DoF is at the boundary or in a depression without outlet.
1862 *
1863 * @code
1864 *   const auto unpack_function =
1865 *   [this, &water_dofs_to_steepest_downhill_neighbor](
1866 *   const typename DoFHandler<dim, spacedim>::active_cell_iterator &cell,
1867 *   const CellLocalData &cell_local_data) {
1868 *   Assert(cell->is_ghost(), ExcInternalError());
1869 *  
1870 *   for (const auto &[source_index, dest_index] : cell_local_data)
1871 *   {
1872 *   Assert(dof_handler.locally_owned_dofs().is_element(source_index) ==
1873 *   false,
1874 *   ExcInternalError());
1875 *   Assert(locally_relevant_dofs.is_element(source_index) == true,
1876 *   ExcInternalError());
1877 *   if ((dest_index == numbers::invalid_dof_index) ||
1878 *   locally_relevant_dofs.is_element(dest_index))
1879 *   water_dofs_to_steepest_downhill_neighbor.insert(
1880 *   {source_index, dest_index});
1881 *   }
1882 *   };
1883 *  
1884 *   GridTools::exchange_cell_data_to_ghosts<CellLocalData>(dof_handler,
1885 *   pack_function,
1886 *   unpack_function);
1887 *  
1888 * @endcode
1889 *
1890 * At this point, we should have gotten information about all locally
1891 * relevant water DoFs where they send their water (if anywhere),
1892 * excluding not locally owned ones that sent water to not locally
1893 * relevant ones -- these are at the outer fringes of the ghost layer
1894 * sending water further afield. In other words, we need to have
1895 * information about all locally active ones and at least some of
1896 * the locally relevant ones. We can check that this is the case:
1897 *
1898 * @code
1899 *   #ifdef DEBUG
1900 *   {
1901 *   Assert(water_dofs_to_steepest_downhill_neighbor.size() <=
1902 *   locally_relevant_water_dofs.n_elements(),
1903 *   ExcInternalError());
1904 *   for (const auto &[src, dst] : water_dofs_to_steepest_downhill_neighbor)
1905 *   Assert(locally_relevant_water_dofs.is_element(src),
1906 *   ExcInternalError());
1907 *  
1908 *   const types::global_dof_index n_elevation_dofs =
1909 *   dof_handler.n_dofs() / 2;
1910 *   IndexSet all_elevation_dofs(dof_handler.n_dofs());
1911 *   all_elevation_dofs.add_range(0, n_elevation_dofs);
1912 *   IndexSet locally_active_water_dofs =
1913 *   DoFTools::extract_locally_active_dofs(dof_handler);
1914 *   locally_active_water_dofs.subtract_set(all_elevation_dofs);
1915 *   for (const auto &locally_active_index : locally_active_water_dofs)
1916 *   Assert(water_dofs_to_steepest_downhill_neighbor.find(
1917 *   locally_active_index) !=
1918 *   water_dofs_to_steepest_downhill_neighbor.end(),
1919 *   ExcInternalError());
1920 *   }
1921 *   #endif
1922 *  
1923 * @endcode
1924 *
1925 * Up to this point, it was useful to work with a std::map, but ultimately
1926 * we want a faster representation. So convert things into a std::vector
1927 * of pairs:
1928 *
1929 * @code
1930 *   local_flow_routing = {water_dofs_to_steepest_downhill_neighbor.begin(),
1931 *   water_dofs_to_steepest_downhill_neighbor.end()};
1932 *  
1933 * @endcode
1934 *
1935 * Finally, we can check that the only depressions we have should be
1936 * on the boundary of the domain. Recall that we marked depressions
1937 * (i.e., nodes that don't give water to any lower-lying neighbor)
1938 * in the src->dst relationships by invalid 'dst' values. This
1939 * should only be the case for 'src' nodes that are on the
1940 * boundary, and we can check that:
1941 *
1942 * @code
1943 *   #ifdef DEBUG
1944 *   {
1945 *   const IndexSet boundary_nodes =
1946 *   DoFTools::extract_boundary_dofs(dof_handler);
1947 *  
1948 *   for (const auto &[src, dst] : local_flow_routing)
1949 *   if (dst == numbers::invalid_dof_index)
1950 *   Assert(boundary_nodes.is_element(src),
1951 *   ExcMessage("Found an interior depression in the DEM."));
1952 *   }
1953 *   #endif
1954 *   }
1955 *  
1956 *  
1957 * @endcode
1958 *
1959 *
1960 * <a name="parallel_flow_routing.cc-ParallelFlowRouterassemble_system"></a>
1961 * <h3>ParallelFlowRouter::assemble_system()</h3>
1962 *
1963
1964 *
1965 * This function assembles the linear system that describes the steady-state
1966 * water flow on the landscape. The system is based on the principle of mass
1967 * conservation: at each point, the water flowing out must equal the water
1968 * flowing in (from rain and from upstream neighbors) minus any water that
1969 * accumulates.
1970 *
1971
1972 *
1973 * The discretized system has the form:
1974 * w_i = r_i + sum_{j: j flows to i} w_j
1975 * where w_i is the water flow rate at node i, r_i is the rainfall at node i,
1976 * and the sum is over all upstream nodes j that flow into node i.
1977 *
1978
1979 *
1980 * In matrix form, this becomes:
1981 * (I - F) * w = r
1982 * where F is the flow routing matrix (defined by the local_flow_routing
1983 * data), I is the identity matrix, w is the vector of water flow rates, and r
1984 * is the rainfall vector.
1985 *
1986
1987 *
1988 * Since we've renumbered the DoFs so that water flows from higher to lower
1989 * elevations, the matrix (I - F) is lower triangular, making it easy to
1990 * solve.
1991 *
1992
1993 *
1994 * Because we have chosen to work with a 2x2 block system where the first
1995 * block corresponds to elevation and the second block corresponds to water
1996 * flow rate, the sparsity patterns and matrix assembly are a bit more
1997 * complicated than in a standard finite element code, but the underlying
1998 * principles are the same. We just have to translate indices correctly and
1999 * make sure to fill the right blocks of the matrix.
2000 *
2001 * @code
2002 *   void
2003 *   ParallelFlowRouter::assemble_system()
2004 *   {
2005 *   TimerOutput::Scope t(computing_timer, "Solver 1: Assemble system");
2006 *   pcout << "Assembling linear system... " << std::endl;
2007 *  
2008 *   BlockDynamicSparsityPattern dsp(locally_relevant_partitioning);
2009 *   for (const types::global_dof_index water_dof_within_block_1 :
2010 *   locally_owned_partitioning[1])
2011 *   dsp.block(1, 1).add(water_dof_within_block_1, water_dof_within_block_1);
2012 *  
2013 *   for (const auto &[src, dst] : local_flow_routing)
2014 *   if (dof_handler.locally_owned_dofs().is_element(dst))
2015 *   dsp.add(dst, src);
2016 *   SparsityTools::distribute_sparsity_pattern(dsp,
2017 *   dof_handler.locally_owned_dofs(),
2018 *   mpi_communicator,
2019 *   locally_relevant_dofs);
2020 *  
2021 * @endcode
2022 *
2023 * Now fill matrix accordingly
2024 *
2025 * @code
2026 *   system_matrix.reinit(locally_owned_partitioning, dsp, mpi_communicator);
2027 *   for (const types::global_dof_index water_dof_within_block_1 :
2028 *   locally_owned_partitioning[1])
2029 *   system_matrix.block(1, 1).set(water_dof_within_block_1,
2030 *   water_dof_within_block_1,
2031 *   1.); // 1s on the diagonal
2032 *   for (const auto &[water_dof, lowest_neighbor] : local_flow_routing)
2033 *   if (dof_handler.locally_owned_dofs().is_element(lowest_neighbor))
2034 *   system_matrix.set(lowest_neighbor,
2035 *   water_dof,
2036 *   -1); // -1s for flow routing
2037 *   system_matrix.compress(VectorOperation::insert);
2038 *  
2039 * @endcode
2040 *
2041 * Then also fill rhs vector:
2042 *
2043 * @code
2044 *   const RainFallRate<spacedim> rainfall_rate;
2045 *   for (const auto &cell : dof_handler.active_cell_iterators())
2046 *   if (cell->is_locally_owned())
2047 *   for (unsigned int v = 0; v < cell->reference_cell().n_vertices(); ++v)
2048 *   {
2049 *   const types::global_dof_index vertex_water_dof =
2050 *   cell->vertex_dof_index(v, 1);
2051 *   system_rhs(vertex_water_dof) +=
2052 *   rainfall_rate.value(cell->vertex(v)) * cell->measure() /
2053 *   cell->n_vertices();
2054 *   }
2055 *   system_rhs.compress(VectorOperation::add);
2056 *   }
2057 *  
2058 *  
2059 * @endcode
2060 *
2061 *
2062 * <a name="parallel_flow_routing.cc-ParallelFlowRouterFlowRoutingMatrix"></a>
2063 * <h3>ParallelFlowRouter::FlowRoutingMatrix</h3>
2064 *
2065
2066 *
2067 * This is a matrix-free operator class that represents the matrix A from the
2068 * discussion in assemble_system(). Rather than storing the matrix explicitly,
2069 * this class implements only the matrix-vector product (via the vmult()
2070 * function), computing the result on the fly from the local_flow_routing data
2071 * structure.
2072 *
2073
2074 *
2075 * Recall that the matrix A encodes the flow routing: A has a -1 in position
2076 * (i, j) if water from node j flows to node i, and 0 elsewhere, plus a +1
2077 * on the diagonal. Each column j has at most one non-zero entry other
2078 * that the diagonal entry (since each node gives water to at most
2079 * one downhill neighbor). More precisely, for each src->dst pair in
2080 * local_flow_routing, we have a -1 in position (dst, src) of the matrix.
2081 *
2082
2083 *
2084 * The vmult() function computes A*X = (I-F)*x = (I*x - F*x) by first
2085 * copying x to y (implementing I*x), then subtracting the contributions
2086 * from F. To compute F*x efficiently, we iterate over the src->dst pairs,
2087 * and for each one where 'dst' is in the locally owned range, we add
2088 * -x[src] to y[dst].
2089 *
2090 * @code
2091 *   class ParallelFlowRouter::FlowRoutingMatrix
2092 *   {
2093 *   public:
2094 *   FlowRoutingMatrix(
2095 *   const IndexSet &locally_owned_water_dofs,
2096 *   const IndexSet &locally_relevant_water_dofs,
2097 *   const MPI_Comm mpi_communicator,
2098 *   const unsigned int water_dofs_offset,
2099 *   const std::vector<std::pair<types::global_dof_index,
2100 *   types::global_dof_index>> &local_flow_routing)
2101 *   : x_with_ghosts(locally_owned_water_dofs,
2102 *   locally_relevant_water_dofs,
2103 *   mpi_communicator)
2104 *   , my_local_flow_routing(local_flow_routing)
2105 *   {
2106 * @endcode
2107 *
2108 * We got the map from DoFs to downhill neighbors in global DoF
2109 * indices, but we need them in indices relative to the second
2110 * vector block (or the (1,1) matrix block). So shift, unless
2111 * the destination DoF is invalid, indicating that this source
2112 * DoF has no outlet (because it's a depression in the DEM,
2113 * or because it's at the boundary).
2114 *
2115 * @code
2116 *   for (auto &[src, dst] : my_local_flow_routing)
2117 *   {
2118 *   src -= water_dofs_offset;
2119 *   Assert(locally_relevant_water_dofs.is_element(src),
2120 *   ExcInternalError());
2121 *  
2122 *   if (dst != numbers::invalid_dof_index)
2123 *   {
2124 *   dst -= water_dofs_offset;
2125 *   Assert(locally_relevant_water_dofs.is_element(dst),
2126 *   ExcInternalError());
2127 *   }
2128 *   }
2129 *  
2130 * @endcode
2131 *
2132 * If one looks at how the vmult() function below is implemented,
2133 * one realizes that we only need those src->dst relationships
2134 * where 'dst' is a valid DoF index and is in fact in the
2135 * locally owned range (it is the row index in the matrix,
2136 * and consequently that part of the output vector we fill
2137 * on the current process). To make this cheaper, we erase all others
2138 * at this point, so we don't have to check any more there:
2139 *
2140 * @code
2141 *   Assert(locally_owned_water_dofs.is_contiguous(), ExcInternalError());
2142 *   const auto it =
2143 *   std::remove_if(my_local_flow_routing.begin(),
2144 *   my_local_flow_routing.end(),
2145 *   [&locally_owned_water_dofs](
2146 *   const std::pair<types::global_dof_index,
2147 *   types::global_dof_index> &src_dst) {
2148 *   const types::global_dof_index dst = src_dst.second;
2149 *   return ((dst == numbers::invalid_dof_index) ||
2150 *   !locally_owned_water_dofs.is_element(dst));
2151 *   });
2152 *   my_local_flow_routing.erase(it, my_local_flow_routing.end());
2153 *  
2154 * @endcode
2155 *
2156 * Pre-compute the write buffers that will be used in vmult(). These
2157 * buffers store the source and destination indices for efficient batch
2158 * operations on the vector. This approach is more efficient than looping
2159 * over the flow routing pairs in each vmult() call because it allows us
2160 * to look up many vector entries all at once, rather than having to
2161 * translate between global and process-local indices for each vector
2162 * entry we care about individually. The buffers are computed once in the
2163 * constructor and reused for every matrix-vector product call.
2164 *
2165 * @code
2166 *   write_buffer_source_indices.resize(my_local_flow_routing.size());
2167 *   write_buffer_indices.resize(my_local_flow_routing.size());
2168 *   write_buffer_values.resize(my_local_flow_routing.size());
2169 *   unsigned int index = 0;
2170 *   for (const auto &[src, dst] : my_local_flow_routing)
2171 *   {
2172 *   write_buffer_source_indices[index] = src;
2173 *   write_buffer_indices[index] = dst;
2174 *   ++index;
2175 *   }
2176 *   }
2177 *  
2178 *   void
2179 *   vmult(typename VectorType::BlockType &y,
2180 *   const typename VectorType::BlockType &x) const
2181 *   {
2182 *   x_with_ghosts = x;
2183 *  
2184 * @endcode
2185 *
2186 * The src->dst relationship defines the matrix via an entry
2187 * of +1 in the (src,src) position, and a -1 in the
2188 * (dst,src) position -- i.e., each entry in the src->dst
2189 * map defines a column of the matrix.
2190 *
2191
2192 *
2193 * Let us first take care of the +1s on the diagonal. We get
2194 * that by setting y=I*x
2195 *
2196 * @code
2197 *   y = x;
2198 *  
2199 * @endcode
2200 *
2201 * Then we need to add to the locally-owned elements of the y vector
2202 * by multiplying the x vector with the -1's of matrix.
2203 * This means that we need to loop over all elements of the
2204 * map and determine whether the row value of the entries
2205 * mentioned above are in the locally owned range:
2206 *
2207
2208 *
2209 * Rather than looping over each (src, dst) pair and updating y(dst)
2210 * individually, we use a more efficient vectorized approach via write
2211 * buffers. We extract all the source values at once using
2212 * extract_subvector_to(), negate them, and then add them to the
2213 * destination vector using array-based operations. This is much faster
2214 * than scalar operations because it allows better use of the CPU's
2215 * vectorization capabilities. The equivalent but slower code would be:
2216 * for (const auto &[src, dst] : my_local_flow_routing)
2217 * y(dst) -= x_with_ghosts(src);
2218 *
2219 * @code
2220 *   x_with_ghosts.extract_subvector_to(write_buffer_source_indices,
2221 *   write_buffer_values);
2222 *   for (auto &v : write_buffer_values)
2223 *   v = -v;
2224 *   y.add(write_buffer_indices, write_buffer_values);
2225 *  
2226 *   y.compress(VectorOperation::add);
2227 *   }
2228 *  
2229 *   private:
2230 *   mutable typename VectorType::BlockType x_with_ghosts;
2231 *   mutable std::vector<types::global_dof_index> write_buffer_source_indices;
2232 *   mutable std::vector<types::global_dof_index> write_buffer_indices;
2233 *   mutable std::vector<PetscScalar> write_buffer_values;
2234 *  
2235 *   std::vector<std::pair<types::global_dof_index, types::global_dof_index>>
2236 *   my_local_flow_routing;
2237 *   };
2238 *  
2239 *  
2240 * @endcode
2241 *
2242 *
2243 * <a name="parallel_flow_routing.cc-ParallelFlowRouterFlowRoutingPreconditioner"></a>
2244 * <h3>ParallelFlowRouter::FlowRoutingPreconditioner</h3>
2245 *
2246
2247 *
2248 * This class implements an efficient preconditioner for the matrix A=(I-F).
2249 * Since the matrix is triangular (after reordering DoFs from high to low
2250 * elevation), the preconditioner uses a triangular solve to approximate
2251 * the inverse of (I-F).
2252 *
2253
2254 *
2255 * The triangular solve works by processing the DoFs in order. For each
2256 * equation i (corresponding to water flow at node i), we compute:
2257 * y_i = (x_i + y_i) / a_ii
2258 * where a_ii = 1 (the diagonal entries of (I-F) are all 1), and y_i
2259 * accumulates contributions from upstream nodes. We then update downstream
2260 * equations by adding y_i to their y values.
2261 *
2262
2263 *
2264 * Because the matrix is triangular and we process nodes in order from high
2265 * to low elevation, each node's solution depends only on upstream (higher)
2266 * nodes, which have already been processed. This makes the triangular solve
2267 * very efficient and cache-friendly.
2268 *
2269
2270 *
2271 * See the detailed comments in the vmult() function below for a complete
2272 * explanation of how the triangular solve is implemented.
2273 *
2274 * @code
2275 *   class ParallelFlowRouter::FlowRoutingPreconditioner
2276 *   {
2277 *   public:
2278 *   FlowRoutingPreconditioner(
2279 *   const IndexSet &locally_owned_water_dofs,
2280 *   const IndexSet &locally_relevant_water_dofs,
2281 *   const unsigned int water_dofs_offset,
2282 *   const std::vector<std::pair<types::global_dof_index,
2283 *   types::global_dof_index>> &local_flow_routing)
2284 *   : my_local_flow_routing(local_flow_routing)
2285 *   {
2286 * @endcode
2287 *
2288 * We got the map from DoFs to downhill neighbors in global DoF
2289 * indices, but we need them in indices relative to the second
2290 * vector block (or the (1,1) matrix block). So shift, unless
2291 * the destination DoF is -1, indicating that this source
2292 * DoF has no outlet (because it's a depression in the DEM,
2293 * or because it's at the boundary).
2294 *
2295 * @code
2296 *   for (auto &[src, dst] : my_local_flow_routing)
2297 *   {
2298 *   src -= water_dofs_offset;
2299 *   Assert(locally_relevant_water_dofs.is_element(src),
2300 *   ExcInternalError());
2301 *  
2302 *   if (dst != numbers::invalid_dof_index)
2303 *   {
2304 *   dst -= water_dofs_offset;
2305 *   Assert(locally_relevant_water_dofs.is_element(dst),
2306 *   ExcInternalError());
2307 *   }
2308 *   }
2309 *  
2310 * @endcode
2311 *
2312 * Unlike the matrix itself, the preconditioner only looks at
2313 * the diagonal blocks. Recall that for each local routing src->dst,
2314 * we have entries in the (src,src) and (dst,src) position. Both
2315 * of these are in the same column, 'src'. One of the two entries
2316 * is the diagonal entry.
2317 *
2318
2319 *
2320 * This means that for a local routing to affect the diagonal block,
2321 * we have to have 'src' be locally owned. That's enough: if so,
2322 * at least the diagonal entry (and perhaps also the other one) is
2323 * in the locally owned diagonal block of the matrix.
2324 *
2325
2326 *
2327 * So erase all others so that we don't have to check this during
2328 * the vmult() operation:
2329 *
2330 * @code
2331 *   Assert(locally_owned_water_dofs.is_contiguous(), ExcInternalError());
2332 *   const auto it =
2333 *   std::remove_if(my_local_flow_routing.begin(),
2334 *   my_local_flow_routing.end(),
2335 *   [&locally_owned_water_dofs](
2336 *   const std::pair<types::global_dof_index,
2337 *   types::global_dof_index> &src_dst) {
2338 *   const types::global_dof_index src = src_dst.first;
2339 *   return !locally_owned_water_dofs.is_element(src);
2340 *   });
2341 *   my_local_flow_routing.erase(it, my_local_flow_routing.end());
2342 *  
2343 * @endcode
2344 *
2345 * At this point, we should have one routing for each locally owned
2346 * DoF. Check that the number is right:
2347 *
2348 * @code
2349 *   AssertDimension(my_local_flow_routing.size(),
2350 *   locally_owned_water_dofs.n_elements());
2351 *  
2352 * @endcode
2353 *
2354 * One last step: if we have a src->dst pair where we have already
2355 * made sure that 'src' is locally owned, then we know that the
2356 * (src,src) entry that results is in the locally owned diagonal
2357 * block. The second matrix entry is (dst,src), which may or may
2358 * not be in that diagonal block, depending on whether 'dst' is
2359 * locally owned. Of course, 'dst' may also be numbers::invalid_dof_index
2360 * if 'src' simply has no downstream neighbor.
2361 * In the first case, the (dst,src) matrix entry is of no concern to
2362 * us. In the second case, there simply is no second matrix entry.
2363 * In other words, in neither case is there an entry (dst,src) that
2364 * we need to deal with.
2365 *
2366
2367 *
2368 * To make our work easier, we turn the first into the second case
2369 * so that in the vmult() function we need not test for inclusion
2370 * of 'dst' in the index set of locally owned DoFs, but just compare
2371 * with numbers::invalid_dof_index.
2372 *
2373 * @code
2374 *   for (auto &src_dst : my_local_flow_routing)
2375 *   if ((src_dst.second != numbers::invalid_dof_index) &&
2376 *   (locally_owned_water_dofs.is_element(src_dst.second) == false))
2377 *   src_dst.second = numbers::invalid_dof_index;
2378 *   }
2379 *  
2380 *  
2381 *  
2447 *   void
2448 *   vmult(typename VectorType::BlockType &y,
2449 *   const typename VectorType::BlockType &x) const
2450 *   {
2451 *   y = 0;
2452 *   for (const auto &[src, dst] : my_local_flow_routing)
2453 *   {
2454 * @endcode
2455 *
2456 * Solve the 'src'th equation. In the notation from
2457 * above, this reads as
2458 * yk <- xk + yk
2459 * which with this function's variable names translates to
2460 * the following, storing the result in a temporary variable
2461 * for use below:
2462 *
2463 * @code
2464 *   const double yk = (y(src) += x(src));
2465 *  
2466 * @endcode
2467 *
2468 * Update a downstream entry if necessary. Again, in the notation
2469 * from above, this reads as
2470 * yl <- yl + yk
2471 * and so is the following:
2472 *
2473 * @code
2474 *   if (dst != numbers::invalid_dof_index)
2475 *   y(dst) += yk;
2476 *   }
2477 *   y.compress(VectorOperation::add);
2478 *   }
2479 *  
2480 *   private:
2481 *   std::vector<std::pair<types::global_dof_index, types::global_dof_index>>
2482 *   my_local_flow_routing;
2483 *   };
2484 *  
2485 *  
2486 * @endcode
2487 *
2488 *
2489 * <a name="parallel_flow_routing.cc-ParallelFlowRouterIplusminusXMatrixBase"></a>
2490 * <h3>ParallelFlowRouter::IplusminusXMatrixBase</h3>
2491 *
2492
2493 *
2494 * This is a base class for matrix-free operators representing matrices of the
2495 * form (I ± X), where X is related to the flow routing matrix.
2496 *
2497
2498 *
2499 * The class uses a block partitioning of the matrix and DoFs:
2500 * - Diagonal block (locally owned rows and columns)
2501 * - R matrix (locally owned rows, but columns on other processes)
2502 *
2503
2504 *
2505 * The vmult() function computes y=(I ± X)*x by splitting the computation into
2506 * contributions from the locally owned part and the off-process part.
2507 *
2508
2509 *
2510 * The derived classes IplusXMatrix and IminusXMatrix implement the two
2511 * variants of the actual operator.
2512 *
2513 * @code
2514 *   class ParallelFlowRouter::IplusminusXMatrixBase
2515 *   {
2516 *   public:
2517 *   IplusminusXMatrixBase(
2518 *   const IndexSet &locally_owned_water_dofs,
2519 *   const IndexSet &locally_relevant_water_dofs,
2520 *   const MPI_Comm mpi_communicator,
2521 *   const unsigned int water_dofs_offset,
2522 *   const std::vector<std::pair<types::global_dof_index,
2523 *   types::global_dof_index>> &local_flow_routing,
2524 *   const FlowRoutingPreconditioner &flow_routing_preconditioner)
2525 *   : tmp(locally_owned_water_dofs, mpi_communicator)
2526 *   , x_with_ghosts(locally_owned_water_dofs,
2527 *   locally_relevant_water_dofs,
2528 *   mpi_communicator)
2529 *   , my_local_flow_routing(local_flow_routing)
2530 *   , flow_routing_preconditioner(flow_routing_preconditioner)
2531 *   {
2532 * @endcode
2533 *
2534 * We got the map from DoFs to downhill neighbors in global DoF
2535 * indices, but we need them in indices relative to the second
2536 * vector block (or the (1,1) matrix block). So shift, unless
2537 * the destination DoF is -1, indicating that this source
2538 * DoF has no outlet (because it's a depression in the DEM,
2539 * or because it's at the boundary).
2540 *
2541 * @code
2542 *   for (auto &[src, dst] : my_local_flow_routing)
2543 *   {
2544 *   src -= water_dofs_offset;
2545 *   Assert(locally_relevant_water_dofs.is_element(src),
2546 *   ExcInternalError());
2547 *  
2548 *   if (dst != numbers::invalid_dof_index)
2549 *   {
2550 *   dst -= water_dofs_offset;
2551 *   Assert(locally_relevant_water_dofs.is_element(dst),
2552 *   ExcInternalError());
2553 *   }
2554 *   }
2555 *  
2556 * @endcode
2557 *
2558 * Compared to the vmult() function of the FlowRoutingMatrix, where
2559 * we needed all entries that are in the locally owned rows of the
2560 * matrix, for the current matrix all we need are those entries that
2561 * are in the locally owned rows *but not in the locally owned columns*.
2562 * As a consequence, delete not only everything that's not in locally
2563 * owned rows, but *also* those *are* in locally owned columns:
2564 *
2565 * @code
2566 *   Assert(locally_owned_water_dofs.is_contiguous(), ExcInternalError());
2567 *   const auto it =
2568 *   std::remove_if(my_local_flow_routing.begin(),
2569 *   my_local_flow_routing.end(),
2570 *   [&locally_owned_water_dofs](
2571 *   const std::pair<types::global_dof_index,
2572 *   types::global_dof_index> &src_dst) {
2573 *   const types::global_dof_index from = src_dst.first;
2574 *   const types::global_dof_index to = src_dst.second;
2575 *   return (locally_owned_water_dofs.is_element(from) ||
2576 *   ((to == numbers::invalid_dof_index) ||
2577 *   !locally_owned_water_dofs.is_element(to)));
2578 *   });
2579 *   my_local_flow_routing.erase(it, my_local_flow_routing.end());
2580 *  
2581 * @endcode
2582 *
2583 * On a single process, the B matrix is empty: We have only one diagonal
2584 * block that spans the whole matrix, and so there is literally nothing
2585 * left for B. Make sure that is in fact true.
2586 *
2587 * @code
2588 *   Assert((Utilities::MPI::n_mpi_processes(mpi_communicator) > 1) ||
2589 *   (my_local_flow_routing.size() == 0),
2590 *   ExcInternalError());
2591 *  
2592 * @endcode
2593 *
2594 * Pre-compute the write buffers for efficient batch vector operations
2595 * in the vmult() function. This approach avoids repeated allocations and
2596 * allows vectorized extraction and addition operations on the flow
2597 * routing pairs, which is significantly faster than processing them
2598 * one-by-one.
2599 *
2600 * @code
2601 *   write_buffer_source_indices.resize(my_local_flow_routing.size());
2602 *   write_buffer_indices.resize(my_local_flow_routing.size());
2603 *   write_buffer_values.resize(my_local_flow_routing.size());
2604 *   unsigned int index = 0;
2605 *   for (const auto &[src, dst] : my_local_flow_routing)
2606 *   {
2607 *   write_buffer_source_indices[index] = src;
2608 *   write_buffer_indices[index] = dst;
2609 *   ++index;
2610 *   }
2611 *   }
2612 *  
2613 *   protected:
2614 *   mutable typename VectorType::BlockType tmp;
2615 *   mutable typename VectorType::BlockType x_with_ghosts;
2616 *   mutable std::vector<types::global_dof_index> write_buffer_source_indices;
2617 *   mutable std::vector<types::global_dof_index> write_buffer_indices;
2618 *   mutable std::vector<PetscScalar> write_buffer_values;
2619 *  
2620 *   std::vector<std::pair<types::global_dof_index, types::global_dof_index>>
2621 *   my_local_flow_routing;
2622 *   const FlowRoutingPreconditioner &flow_routing_preconditioner;
2623 *   };
2624 *  
2625 *  
2626 * @endcode
2627 *
2628 *
2629 * <a name="parallel_flow_routing.cc-ParallelFlowRouterIplusXMatrix"></a>
2630 * <h3>ParallelFlowRouter::IplusXMatrix</h3>
2631 *
2632
2633 *
2634 * This class represents the matrix (I + X), where X is derived from the
2635 * flow routing matrix. It is used in certain implicit time-stepping schemes.
2636 * The class simply inherits from IplusminusXMatrixBase.
2637 *
2638 * @code
2639 *   class ParallelFlowRouter::IplusXMatrix
2640 *   : public ParallelFlowRouter::IplusminusXMatrixBase
2641 *   {
2642 *   public:
2643 *   IplusXMatrix(const IndexSet &locally_owned_water_dofs,
2644 *   const IndexSet &locally_relevant_water_dofs,
2645 *   const MPI_Comm mpi_communicator,
2646 *   const unsigned int water_dofs_offset,
2647 *   const std::vector<
2648 *   std::pair<types::global_dof_index, types::global_dof_index>>
2649 *   &local_flow_routing,
2650 *   const FlowRoutingPreconditioner &flow_routing_preconditioner)
2651 *   : IplusminusXMatrixBase(locally_owned_water_dofs,
2652 *   locally_relevant_water_dofs,
2653 *   mpi_communicator,
2654 *   water_dofs_offset,
2655 *   local_flow_routing,
2656 *   flow_routing_preconditioner)
2657 *   {}
2658 *  
2659 *  
2660 *   void
2661 *   vmult(typename VectorType::BlockType &y,
2662 *   const typename VectorType::BlockType &x) const
2663 *   {
2664 * @endcode
2665 *
2666 * Start by importing ghost entries:
2667 *
2668 * @code
2669 *   x_with_ghosts = x;
2670 *  
2671 * @endcode
2672 *
2673 * The from->to relationship defines the matrix via an entry
2674 * of +1 in the (from,from) position, and a -1 in the
2675 * (to,from) position -- i.e., each entry in the from->to
2676 * map defines a column of the matrix.
2677 *
2678
2679 *
2680 * The +1s all lie outside the D matrix, so we need not think
2681 * about these entries at all, and we can start with a zero vector:
2682 *
2683 * @code
2684 *   tmp = 0;
2685 *  
2686 * @endcode
2687 *
2688 * Then we need to add to the locally-owned elements of the dst vector
2689 * by multiplying the src vector with the -1's of matrix.
2690 * This means that we need to loop over all elements of the
2691 * map and determine whether the row value of the entries
2692 * mentioned above are in the locally owned range:
2693 *
2694
2695 *
2696 * We use the write buffer optimization to efficiently compute the
2697 * matrix-vector product with vectorized batch operations. The code below
2698 * is equivalent to:
2699 * const std::pair<types::global_dof_index, types::global_dof_index>
2700 * locally_owned_range = tmp.local_range();
2701 * for (const auto &[src, dst] : my_local_flow_routing)
2702 * {
2704 * (dst >= locally_owned_range.first) &&
2705 * (dst < locally_owned_range.second),
2706 * ExcInternalError());
2707 * tmp(dst) -= x_with_ghosts(src);
2708 * }
2709 * However, this version is much more efficient because it uses array
2710 * operations that can be vectorized by the CPU, rather than scalar
2711 * operations in a loop.
2712 *
2713 * @code
2714 *   x_with_ghosts.extract_subvector_to(write_buffer_source_indices,
2715 *   write_buffer_values);
2716 *   for (auto &v : write_buffer_values)
2717 *   v = -v;
2718 *   tmp.add(write_buffer_indices, write_buffer_values);
2719 *  
2720 *   tmp.compress(VectorOperation::add);
2721 *  
2722 * @endcode
2723 *
2724 * We have now computed tmp=R*x. Let's apply D^{-1} to it
2725 * to get X*x:
2726 *
2727 * @code
2728 *   flow_routing_preconditioner.vmult(y, tmp);
2729 *  
2730 * @endcode
2731 *
2732 * Finally, we need to add to it src so that we get (I+X)*src:
2733 *
2734 * @code
2735 *   y += x;
2736 *   }
2737 *   };
2738 *  
2739 *  
2740 * @endcode
2741 *
2742 *
2743 * <a name="parallel_flow_routing.cc-ParallelFlowRouterIminusXMatrix"></a>
2744 * <h3>ParallelFlowRouter::IminusXMatrix</h3>
2745 *
2746
2747 *
2748 * This class represents the matrix (I - X), where X is derived from the
2749 * flow routing matrix. The implementation is very similar to IplusXMatrix,
2750 * but with the opposite sign for X.
2751 *
2752 * @code
2753 *   class ParallelFlowRouter::IminusXMatrix
2754 *   : public ParallelFlowRouter::IplusminusXMatrixBase
2755 *   {
2756 *   public:
2757 *   IminusXMatrix(const IndexSet &locally_owned_water_dofs,
2758 *   const IndexSet &locally_relevant_water_dofs,
2759 *   const MPI_Comm mpi_communicator,
2760 *   const unsigned int water_dofs_offset,
2761 *   const std::vector<
2762 *   std::pair<types::global_dof_index, types::global_dof_index>>
2763 *   &local_flow_routing,
2764 *   const FlowRoutingPreconditioner &flow_routing_preconditioner)
2765 *   : IplusminusXMatrixBase(locally_owned_water_dofs,
2766 *   locally_relevant_water_dofs,
2767 *   mpi_communicator,
2768 *   water_dofs_offset,
2769 *   local_flow_routing,
2770 *   flow_routing_preconditioner)
2771 *   {}
2772 *  
2773 *  
2774 *   void
2775 *   vmult(typename VectorType::BlockType &y,
2776 *   const typename VectorType::BlockType &x) const
2777 *   {
2778 *   x_with_ghosts = x;
2779 *  
2780 *   tmp = 0;
2781 *   x_with_ghosts.extract_subvector_to(write_buffer_source_indices,
2782 *   write_buffer_values);
2783 *   tmp.add(write_buffer_indices, write_buffer_values);
2784 *  
2785 *   tmp.compress(VectorOperation::add);
2786 *  
2787 * @endcode
2788 *
2789 * We have now computed tmp=-R*x. Let's apply D^{-1} to it
2790 * to get -X*x:
2791 *
2792 * @code
2793 *   flow_routing_preconditioner.vmult(y, tmp);
2794 *  
2795 * @endcode
2796 *
2797 * Finally, we need to add to it src so that we get (I-X)*src:
2798 *
2799 * @code
2800 *   y += x;
2801 *   }
2802 *   };
2803 *  
2804 *  
2805 * @endcode
2806 *
2807 *
2808 * <a name="parallel_flow_routing.cc-ParallelFlowRouterassemble_matrix_free_operators"></a>
2809 * <h3>ParallelFlowRouter::assemble_matrix_free_operators()</h3>
2810 *
2811
2812 *
2813 * Rather than assembling the full matrix (which could be very large in
2814 * parallel), this function creates "matrix-free" operators that compute
2815 * matrix-vector products implicitly. We will then be able to compare
2816 * all of these approaches.
2817 *
2818
2819 *
2820 * Specifically, this function creates three key objects:
2821 * 1. A FlowRoutingMatrix that represents the matrix F described in
2822 * assemble_system() above.
2823 * 2. A FlowRoutingPreconditioner that approximates the inverse of (I - F)
2824 * using a fast triangular solve.
2825 * 3. A pair of matrices I+X and I-X.
2826 *
2827
2828 *
2829 * These matrix-free operators are used in the solve() function to perform
2830 * iterative linear solves without ever explicitly storing the full matrix.
2831 *
2832 * @code
2833 *   void
2834 *   ParallelFlowRouter::assemble_matrix_free_operators()
2835 *   {
2836 *   TimerOutput::Scope t(computing_timer,
2837 *   "Solver 2: Assemble matrix-free operators");
2838 *   pcout << "Assembling matrix-free operators... " << std::endl;
2839 *  
2840 *   flow_routing_matrix = std::make_unique<const FlowRoutingMatrix>(
2841 *   locally_owned_partitioning[1],
2842 *   locally_relevant_partitioning[1],
2843 *   mpi_communicator,
2844 *   locally_relevant_partitioning[0].size(),
2845 *   local_flow_routing);
2846 *  
2847 *   flow_routing_preconditioner =
2848 *   std::make_unique<const FlowRoutingPreconditioner>(
2849 *   locally_owned_partitioning[1],
2850 *   locally_relevant_partitioning[1],
2851 *   locally_relevant_partitioning[0].size(),
2852 *   local_flow_routing);
2853 *  
2854 *   I_plus_X_matrix = std::make_unique<const IplusXMatrix>(
2855 *   locally_owned_partitioning[1],
2856 *   locally_relevant_partitioning[1],
2857 *   mpi_communicator,
2858 *   locally_relevant_partitioning[0].size(),
2859 *   local_flow_routing,
2860 *   *flow_routing_preconditioner);
2861 *  
2862 *   I_minus_X_matrix = std::make_unique<const IminusXMatrix>(
2863 *   locally_owned_partitioning[1],
2864 *   locally_relevant_partitioning[1],
2865 *   mpi_communicator,
2866 *   locally_relevant_partitioning[0].size(),
2867 *   local_flow_routing,
2868 *   *flow_routing_preconditioner);
2869 *   }
2870 *  
2871 *  
2872 *  
2873 * @endcode
2874 *
2875 *
2876 * <a name="parallel_flow_routing.cc-ParallelFlowRoutersolve"></a>
2877 * <h3>ParallelFlowRouter::solve()</h3>
2878 *
2879
2880 *
2881 * This function solves the linear system assembled in assemble_system() to
2882 * find the steady-state water flow rates at all nodes on the mesh. Since we
2883 * have renumbered the degrees of freedom from high to low elevation, the
2884 * system matrix is lower triangular, and we can solve it very efficiently.
2885 *
2886
2887 *
2888 * The function uses an iterative Richardson solver with a preconditioner
2889 * derived from the triangular structure of the matrix. Because the matrix
2890 * is triangular, a simple SOR preconditioner with a relaxation factor of
2891 * 1.0 is equivalent to the triangular solve.
2892 *
2893
2894 *
2895 * The function then solves the system three more times using the matrix-free
2896 * operators defined above, and compares the results to verify that they all
2897 * give the same solution.
2898 *
2899 * @code
2900 *   void
2901 *   ParallelFlowRouter::solve()
2902 *   {
2903 *   pcout << "Solving for global water routing... " << std::endl;
2904 *  
2905 * @endcode
2906 *
2907 * ---------------- Solve matrix-based -------------------------
2908 *
2909 * @code
2910 *   VectorType completely_distributed_solution_matrix_based(
2911 *   locally_owned_partitioning, mpi_communicator);
2912 *   {
2913 *   TimerOutput::Scope t(computing_timer,
2914 *   "Solver 1: Solve for water matrix-based");
2915 *  
2916 *   SolverControl solver_control(dof_handler.n_dofs() / 2,
2917 *   1e-6 * system_rhs.block(1).l2_norm());
2919 *  
2920 *   PETScWrappers::PreconditionSOR preconditioner;
2921 *   preconditioner.initialize(system_matrix.block(1, 1));
2922 *  
2923 *   solver.solve(system_matrix.block(1, 1),
2924 *   completely_distributed_solution_matrix_based.block(1),
2925 *   system_rhs.block(1),
2926 *   preconditioner);
2927 *  
2928 *   pcout << " Solved matrix-based in " << solver_control.last_step()
2929 *   << " iterations." << std::endl;
2930 *   }
2931 *  
2932 *  
2933 * @endcode
2934 *
2935 * ---------------- Solve matrix-free -------------------------
2936 *
2937 * @code
2938 *   VectorType completely_distributed_solution_matrix_free(
2939 *   locally_owned_partitioning, mpi_communicator);
2940 *   {
2941 *   TimerOutput::Scope t(computing_timer,
2942 *   "Solver 2: Solve for water matrix-free");
2943 *  
2944 *   SolverControl solver_control(dof_handler.n_dofs() / 2,
2945 *   1e-6 * system_rhs.block(1).l2_norm());
2947 *  
2948 *   solver.solve(*flow_routing_matrix,
2949 *   completely_distributed_solution_matrix_free.block(1),
2950 *   system_rhs.block(1),
2951 *   *flow_routing_preconditioner);
2952 *  
2953 *   pcout << " Solved matrix-free in " << solver_control.last_step()
2954 *   << " iterations." << std::endl;
2955 *   }
2956 *  
2957 * @endcode
2958 *
2959 * ---------------- Solve via (I+X)x=D^{-1}b -------------------------
2960 *
2961 * @code
2962 *   VectorType completely_distributed_solution_IplusX(
2963 *   locally_owned_partitioning, mpi_communicator);
2964 *   {
2965 *   TimerOutput::Scope t(computing_timer, "Solver 3: Solve for water I+X");
2966 *  
2967 *   typename VectorType::BlockType Dinv_times_rhs(
2968 *   locally_owned_partitioning[1], mpi_communicator);
2969 *   flow_routing_preconditioner->vmult(Dinv_times_rhs, system_rhs.block(1));
2970 *  
2971 *   SolverControl solver_control(dof_handler.n_dofs() / 2,
2972 *   1e-6 * Dinv_times_rhs.l2_norm());
2974 *  
2975 *   solver.solve(*I_plus_X_matrix,
2976 *   completely_distributed_solution_IplusX.block(1),
2977 *   Dinv_times_rhs,
2979 *  
2980 *   pcout << " Solved I+X-based in " << solver_control.last_step()
2981 *   << " iterations." << std::endl;
2982 *   }
2983 *  
2984 * @endcode
2985 *
2986 * ---------------- Solve via (I-X)(I+X)x=(I-X)D^{-1}b -------------------
2987 *
2988 * @code
2989 *   VectorType completely_distributed_solution_IminusX_IplusX(
2990 *   locally_owned_partitioning, mpi_communicator);
2991 *   {
2992 *   TimerOutput::Scope t(computing_timer,
2993 *   "Solver 4: Solve for water (I-X)(I+X)");
2994 *  
2995 *   typename VectorType::BlockType Dinv_times_rhs(
2996 *   locally_owned_partitioning[1], mpi_communicator);
2997 *   flow_routing_preconditioner->vmult(Dinv_times_rhs, system_rhs.block(1));
2998 *  
2999 *   SolverControl solver_control(dof_handler.n_dofs() / 2,
3000 *   1e-6 * Dinv_times_rhs.l2_norm());
3002 *  
3003 *   solver.solve(*I_plus_X_matrix,
3004 *   completely_distributed_solution_IminusX_IplusX.block(1),
3005 *   Dinv_times_rhs,
3006 *   *I_minus_X_matrix);
3007 *  
3008 *   pcout << " Solved (I-X)(I+X)-based in " << solver_control.last_step()
3009 *   << " iterations." << std::endl;
3010 *   }
3011 *  
3012 *   locally_relevant_solution.block(1) =
3013 *   completely_distributed_solution_matrix_free.block(1);
3014 *  
3015 * @endcode
3016 *
3017 * ----------- Now make sure the solutions agree:
3018 *
3019 * @code
3020 *   completely_distributed_solution_matrix_free -=
3021 *   completely_distributed_solution_matrix_based;
3022 *   pcout << " Relative error between matrix-based and matrix-free: "
3023 *   << completely_distributed_solution_matrix_free.l2_norm() /
3024 *   completely_distributed_solution_matrix_based.l2_norm()
3025 *   << std::endl;
3026 *  
3027 *   completely_distributed_solution_IplusX -=
3028 *   completely_distributed_solution_matrix_based;
3029 *   pcout << " Relative error between matrix-based and I+X solution: "
3030 *   << completely_distributed_solution_IplusX.l2_norm() /
3031 *   completely_distributed_solution_matrix_based.l2_norm()
3032 *   << std::endl;
3033 *  
3034 *   completely_distributed_solution_IminusX_IplusX -=
3035 *   completely_distributed_solution_matrix_based;
3036 *   pcout << " Relative error between matrix-based and (I-X)(I+X) solution: "
3037 *   << completely_distributed_solution_IminusX_IplusX.l2_norm() /
3038 *   completely_distributed_solution_matrix_based.l2_norm()
3039 *   << std::endl;
3040 *   }
3041 *  
3042 *  
3043 * @endcode
3044 *
3045 *
3046 * <a name="parallel_flow_routing.cc-ParallelFlowRoutercheck_conservation_for_waterflow_system"></a>
3047 * <h3>ParallelFlowRouter::check_conservation_for_waterflow_system()</h3>
3048 *
3049
3050 *
3051 * This function verifies that the solution satisfies the principle of mass
3052 * conservation for water. Specifically, it checks that the total water input
3053 * (from rainfall) equals the total water output (flowing out of the domain).
3054 *
3055
3056 *
3057 * The function computes:
3058 * - The total water input by integrating the rainfall rate over the domain
3059 * - The total water output by summing the water flow rates at boundary nodes
3060 *
3061
3062 *
3063 * If these two quantities differ by more than a small tolerance (currently
3064 * 1%), the function throws an exception, indicating a problem with the
3065 * solution.
3066 *
3067
3068 *
3069 * This is a valuable diagnostic check: in a correctly formulated and solved
3070 * system, mass should be strictly conserved (up to numerical errors). If
3071 * conservation is violated, it indicates an error in problem setup, assembly,
3072 * or solution.
3073 *
3074 * @code
3075 *   void
3076 *   ParallelFlowRouter::check_conservation_for_waterflow_system(
3077 *   const VectorType &solution)
3078 *   {
3079 *   TimerOutput::Scope t(computing_timer, "Water conservation check");
3080 *  
3081 *   const QGauss<dim> quadrature_formula(fe.degree + 1);
3082 *   FEValues<dim, spacedim> fe_values(fe,
3083 *   quadrature_formula,
3086 *  
3087 *   const RainFallRate<spacedim> rainfall_rate;
3088 *   double input_from_rain_rate = 0.0;
3089 *   for (const auto &cell : dof_handler.active_cell_iterators())
3090 *   if (cell->is_locally_owned())
3091 *   {
3092 *   fe_values.reinit(cell);
3093 *  
3094 *   for (const unsigned int q : fe_values.quadrature_point_indices())
3095 *   input_from_rain_rate +=
3096 *   rainfall_rate.value(fe_values.quadrature_point(q)) *
3097 *   fe_values.JxW(q);
3098 *   }
3099 *  
3100 *   input_from_rain_rate =
3101 *   Utilities::MPI::sum(input_from_rain_rate, mpi_communicator);
3102 *  
3103 *  
3104 * @endcode
3105 *
3106 * Now also check the outflow. Water flows out of the domain at points
3107 * that (i) are at the boundary, and (ii) have no lower neighbors. We
3108 * have built the DEM so that it has no local depressions in the
3109 * interior of the domain, so we only have to check (ii). In the
3110 * local water routing table, this is indicated by (src->dst) pairs
3111 * where 'dst' is an invalid_dof_index. The only thing we have to pay
3112 * attention to is that we only count the locally owned DoFs to
3113 * avoid double-counting:
3114 *
3115 * @code
3116 *   double water_outflow_rate = 0;
3117 *   for (const auto &[src, dst] : local_flow_routing)
3118 *   if ((dst == numbers::invalid_dof_index) &&
3119 *   dof_handler.locally_owned_dofs().is_element(src))
3120 *   water_outflow_rate += solution(src);
3121 *  
3122 *   water_outflow_rate =
3123 *   Utilities::MPI::sum(water_outflow_rate, mpi_communicator);
3124 *  
3125 *   const double error_abs =
3126 *   std::abs(input_from_rain_rate - water_outflow_rate);
3127 *   const double error_rel = std::abs(error_abs / input_from_rain_rate);
3128 *   pcout << "Conservation check (water)" << std::endl
3129 *   << " Input: " << input_from_rain_rate << std::endl
3130 *   << " Output: " << water_outflow_rate << std::endl
3131 *   << " Relative error: " << error_rel << std::endl;
3132 *  
3133 *   AssertThrow(error_rel < 1e-2,
3134 *   ExcMessage("Conservation of water rate not satisfied."));
3135 *   }
3136 *  
3137 *  
3138 * @endcode
3139 *
3140 *
3141 * <a name="parallel_flow_routing.cc-ParallelFlowRouteroutput_results"></a>
3142 * <h3>ParallelFlowRouter::output_results()</h3>
3143 *
3144
3145 *
3146 * This function writes the solution to VTU output files for visualization.
3147 * The function also adds the subdomain ID of each cell, which is useful for
3148 * visualizing the parallel partitioning of the mesh across MPI processes.
3149 * This can help verify that the partitioning is reasonably balanced.
3150 *
3151
3152 *
3153 * In parallel, each process writes its local portion of the solution to a
3154 * separate file, and a master PVTU file is created that ties all the pieces
3155 * together for visualization in tools like ParaView or VisIt.
3156 *
3157 * @code
3158 *   void
3159 *   ParallelFlowRouter::output_results()
3160 *   {
3161 *   TimerOutput::Scope t(computing_timer, "Output");
3162 *   pcout << "Writing output... " << std::flush;
3163 *  
3164 *   const std::vector<std::string> solution_names = {"elevation",
3165 *   "water_flow_rate"};
3166 *   const std::vector<DataComponentInterpretation::DataComponentInterpretation>
3167 *   data_component_interpretation = {
3170 *  
3171 *   DataOut<dim, spacedim> data_out;
3172 *   data_out.attach_dof_handler(dof_handler);
3173 *   data_out.add_data_vector(locally_relevant_solution,
3174 *   solution_names,
3176 *   data_component_interpretation);
3177 *  
3178 *   Vector<float> subdomain(triangulation.n_active_cells());
3179 *   for (unsigned int i = 0; i < subdomain.size(); ++i)
3180 *   subdomain(i) = triangulation.locally_owned_subdomain();
3181 *   data_out.add_data_vector(subdomain, "subdomain");
3182 *  
3183 *   data_out.build_patches();
3184 *  
3185 *   data_out.write_vtu_with_pvtu_record(
3186 *   "./", "solution", 0, mpi_communicator, 2);
3187 *   }
3188 *  
3189 *  
3190 *  
3191 * @endcode
3192 *
3193 *
3194 * <a name="parallel_flow_routing.cc-ParallelFlowRouterrun"></a>
3195 * <h3>ParallelFlowRouter::run()</h3>
3196 *
3197
3198 *
3199 * This is the main entry point for the flow routing solver. It orchestrates
3200 * all the steps needed to solve the water flow routing problem:
3201 *
3202
3203 *
3204 * 1. Create the computational mesh (`make_grid()`)
3205 * 2. Set up the finite element spaces and DoF numbering (`setup_dofs()`)
3206 * 3. Interpolate the digital elevation model onto the mesh
3207 * (`interpolate_initial_elevation()`)
3208 * 4. Renumber DoFs so water flows from high to low elevation
3209 * (`sort_dofs_high_to_low()`)
3210 * 5. Determine which downhill neighbor each node flows to
3211 * (`compute_local_flow_routing()`)
3212 * 6. Assemble the linear system for steady-state flow (`assemble_system()`)
3213 * 7. Set up matrix-free operators for efficient solving
3214 * (`assemble_matrix_free_operators()`)
3215 * 8. Solve the linear system (`solve()`)
3216 * 9. Check conservation of water mass
3217 * (`check_conservation_for_waterflow_system()`)
3218 * 10. Write output for visualization (`output_results()`)
3219 * 11. Print performance statistics (`computing_timer.print_summary()`)
3220 *
3221
3222 *
3223 * The sequence of these steps reflects the logical flow of the algorithm:
3224 * we first set up the geometry and DoFs, then compute the flow routing
3225 * connectivity, then assemble and solve the linear system, and finally
3226 * perform validation and output.
3227 *
3228 * @code
3229 *   void
3230 *   ParallelFlowRouter::run()
3231 *   {
3232 *   make_grid();
3233 *   setup_dofs();
3234 *   interpolate_initial_elevation();
3235 *   sort_dofs_high_to_low();
3236 *  
3237 *   compute_local_flow_routing();
3238 *  
3239 *   assemble_system();
3240 *   assemble_matrix_free_operators();
3241 *  
3242 *   solve();
3243 *  
3244 *   check_conservation_for_waterflow_system(locally_relevant_solution);
3245 *   if (generate_graphical_output)
3246 *   output_results();
3247 *  
3248 * @endcode
3249 *
3250 * Print the time taken for each section of the code, first in the summary
3251 * table and then as individual numbers in one line. This is useful for
3252 * creating graphs of run times.
3253 *
3254 * @code
3255 *   computing_timer.print_summary();
3256 *   pcout << "Times per section: ";
3257 *   for (const auto &[name, time] :
3258 *   computing_timer.get_summary_data(TimerOutput::total_wall_time))
3259 *   pcout << time << ' ';
3260 *   pcout << std::endl;
3261 *   }
3262 *   } // namespace ParallelFlowRouting
3263 *  
3264 *  
3265 *  
3266 * @endcode
3267 *
3268 *
3269 * <a name="parallel_flow_routing.cc-Themainfunction"></a>
3270 * <h3>The main() function</h3>
3271 *
3272
3273 *
3274 * The main function of the program is quite simple. It initializes MPI for
3275 * parallel execution, creates a ParallelFlowRouter object that registers all
3276 * necessary parameters, and then runs the flow routing algorithm.
3277 *
3278
3279 *
3280 * The parameters can be provided in a file (by passing the filename as a
3281 * command-line argument) or are left at their defaults.
3282 *
3283
3284 *
3285 * The function includes basic error handling: if an exception occurs during
3286 * the computation, it prints an error message and exits gracefully with a
3287 * non-zero return code.
3288 *
3289 * @code
3290 *   int
3291 *   main(int argc, char *argv[])
3292 *   {
3293 *   try
3294 *   {
3295 *   using namespace dealii;
3296 *  
3297 *   Utilities::MPI::MPI_InitFinalize mpi_initialization(argc, argv, 1);
3298 *  
3299 * @endcode
3300 *
3301 * Create the problem object (this registers parameters with
3303 *
3304 * @code
3305 *   ParallelFlowRouting::ParallelFlowRouter problem;
3306 *  
3307 * @endcode
3308 *
3309 * Parse parameters from file if provided
3310 *
3311 * @code
3312 *   if (argc > 1)
3314 *   else
3316 *  
3317 *   problem.run();
3318 *   }
3319 *   catch (std::exception &exc)
3320 *   {
3321 *   std::cerr << std::endl
3322 *   << std::endl
3323 *   << "----------------------------------------------------"
3324 *   << std::endl;
3325 *   std::cerr << "Exception on processing: " << std::endl
3326 *   << exc.what() << std::endl
3327 *   << "Aborting!" << std::endl
3328 *   << "----------------------------------------------------"
3329 *   << std::endl;
3330 *  
3331 *   return 1;
3332 *   }
3333 *   catch (...)
3334 *   {
3335 *   std::cerr << std::endl
3336 *   << std::endl
3337 *   << "----------------------------------------------------"
3338 *   << std::endl;
3339 *   std::cerr << "Unknown exception!" << std::endl
3340 *   << "Aborting!" << std::endl
3341 *   << "----------------------------------------------------"
3342 *   << std::endl;
3343 *   return 1;
3344 *   }
3345 *  
3346 *   return 0;
3347 *   }
3348 * @endcode
3349
3350
3351*/
*  iterator end()
*  *  for(const auto &cell :triangulation.active_cell_iterators())
*  const Number radius
*  *  int main(int argc, char **argv)
*  *  iterator begin()
*  x_component_mask set(0, true)
*  *  *  struct InterferenceTaperTransform *  
***mech_lbc_system increment_interpolation_handlers push_back(scale_z_handler)
void attach_dof_handler(const DoFHandler< dim, spacedim > &)
Definition fe_q.h:552
virtual RangeNumberType value(const Point< dim > &p, const unsigned int component=0) const
IndexSet get_view(const size_type begin, const size_type end) const
Definition index_set.cc:295
void initialize(const MatrixBase &matrix, const AdditionalData &additional_data=AdditionalData())
static void initialize(const std::string &filename="", const std::string &output_filename="", const ParameterHandler::OutputStyle output_style_for_output_filename=ParameterHandler::Short, ParameterHandler &prm=ParameterAcceptor::prm, const ParameterHandler::OutputStyle output_style_for_filename=ParameterHandler::DefaultStyle)
Definition point.h:111
@ wall_times
Definition timer.h:753
Point< 2 > second
Definition grid_out.cc:4640
Point< 2 > first
Definition grid_out.cc:4639
unsigned int level
Definition grid_out.cc:4642
unsigned int vertex_indices[2]
#define Assert(cond, exc)
#define AssertDimension(dim1, dim2)
#define AssertThrow(cond, exc)
typename ActiveSelector::active_cell_iterator active_cell_iterator
void loop(IteratorType begin, std_cxx20::type_identity_t< IteratorType > end, DOFINFO &dinfo, INFOBOX &info, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &)> &cell_worker, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &)> &boundary_worker, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &, typename INFOBOX::CellInfo &)> &face_worker, AssemblerType &assembler, const LoopControl &lctrl=LoopControl())
Definition loop.h:562
@ update_values
Shape function values.
@ update_JxW_values
Transformed quadrature weights.
@ update_quadrature_points
Transformed quadrature points.
std::vector< index_type > data
Definition mpi.cc:734
std::size_t size
Definition mpi.cc:733
const Event initial
Definition event.cc:69
Expression atan2(const Expression &y, const Expression &x)
Expression operator>(const Expression &lhs, const Expression &rhs)
void component_wise(DoFHandler< dim, spacedim > &dof_handler, const std::vector< unsigned int > &target_component=std::vector< unsigned int >())
void downstream(DoFHandler< dim, spacedim > &dof_handler, const Tensor< 1, spacedim > &direction, const bool dof_wise_renumbering=false)
void random(DoFHandler< dim, spacedim > &dof_handler)
IndexSet extract_boundary_dofs(const DoFHandler< dim, spacedim > &dof_handler, const ComponentMask &component_mask={}, const std::set< types::boundary_id > &boundary_ids={})
Definition dof_tools.cc:619
IndexSet extract_locally_relevant_dofs(const DoFHandler< dim, spacedim > &dof_handler)
std::vector< types::global_dof_index > count_dofs_per_fe_block(const DoFHandler< dim, spacedim > &dof, const std::vector< unsigned int > &target_block=std::vector< unsigned int >())
void interpolate(const DoFHandler< dim, spacedim > &dof1, const InVector &u1, const DoFHandler< dim, spacedim > &dof2, OutVector &u2)
void reference_cell(Triangulation< dim, spacedim > &tria, const ReferenceCell< dim > &reference_cell)
void subdivided_hyper_rectangle(Triangulation< dim, spacedim > &tria, const std::vector< unsigned int > &repetitions, const Point< dim > &p1, const Point< dim > &p2, const bool colorize=false)
void transform(const Transformation &transformation, Triangulation< dim, spacedim > &triangulation)
void shift(const Tensor< 1, spacedim > &shift_vector, Triangulation< dim, spacedim > &triangulation)
void exchange_cell_data_to_ghosts(const MeshType &mesh, const std::function< std::optional< DataType >(const typename MeshType::active_cell_iterator &)> &pack, const std::function< void(const typename MeshType::active_cell_iterator &, const DataType &)> &unpack, const std::function< bool(const typename MeshType::active_cell_iterator &)> &cell_filter=always_return< typename MeshType::active_cell_iterator, bool >{true})
constexpr char O
@ matrix
Contents is actually a matrix.
@ diagonal
Matrix is diagonal.
constexpr char N
constexpr types::blas_int zero
constexpr char A
constexpr types::blas_int one
Tpetra::Vector< Number, LO, GO, NodeType< MemorySpace > > VectorType
Tpetra::CrsMatrix< Number, LO, GO, NodeType< MemorySpace > > MatrixType
double norm(const FEValuesBase< dim > &fe, const ArrayView< const std::vector< Tensor< 1, dim > > > &Du)
Definition divergence.h:469
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
Definition utilities.cc:210
Tensor< 2, dim, Number > w(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
SymmetricTensor< 2, dim, Number > b(const Tensor< 2, dim, Number > &F)
Tensor< 2, dim, Number > F(const Tensor< 2, dim, Number > &Grad_u)
Number angle(const Tensor< 1, spacedim, Number > &a, const Tensor< 1, spacedim, Number > &b)
*  *  *  ScaleZFunction< dim, Number, components >::ScaleZFunction *  component(component)
*  *  if(update_pressure &update_flags) *  compute_pressure(constitutive_request
*  *  *  *  std::vector< Number > ThermoPlasticMaterial< dim, ViscoplasticYieldLaw, Number >::get_state_parameters   const
void apply(const Kokkos::TeamPolicy< MemorySpace::Default::kokkos_space::execution_space >::member_type &team_member, const Kokkos::View< Number *, ShapeDataMemorySpace > shape_data, const ViewTypeIn in, ViewTypeOut out)
void partition(const SparsityPattern &sparsity_pattern, const unsigned int n_partitions, std::vector< unsigned int > &partition_indices, const Partitioner partitioner=Partitioner::metis)
constexpr ReturnType< rank, T >::value_type & extract(T &t, const ArrayType &indices)
T sum(const T &t, const MPI_Comm mpi_communicator)
unsigned int n_mpi_processes(const MPI_Comm mpi_communicator)
Definition mpi.cc:103
T min(const T &t, const MPI_Comm mpi_communicator)
std::vector< T > all_gather(const MPI_Comm comm, const T &object_to_send)
unsigned int this_mpi_process(const MPI_Comm mpi_communicator)
Definition mpi.cc:118
T reduce(const T &local_value, const MPI_Comm comm, const std::function< T(const T &, const T &)> &combiner, const unsigned int root_process=0)
T broadcast(const MPI_Comm comm, const T &object_to_send, const unsigned int root_process=0)
void interpolate(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const Function< spacedim, typename VectorType::value_type > &function, VectorType &vec, const ComponentMask &component_mask={}, const unsigned int level=numbers::invalid_unsigned_int)
void run(const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length, const unsigned int chunk_size)
bool check(const ConstraintKinds kind_in, const unsigned int dim)
void copy(const T *begin, const T *end, U *dest)
int(&) functions(const void *v1, const void *v2)
void assemble(const MeshWorker::DoFInfoBox< dim, DOFINFO > &dinfo, A *assembler)
Definition loop.h:68
void reinit(MatrixBlock< MatrixType > &v, const BlockSparsityPattern &p)
constexpr types::global_dof_index invalid_dof_index
Definition types.h:259
constexpr double PI
Definition numbers.h:240
STL namespace.
::VectorizedArray< Number, width > min(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > max(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > cos(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sqrt(const ::VectorizedArray< Number, width > &)
Definition types.h:30
unsigned int global_dof_index
Definition types.h:92