Loading...
Searching...
No Matches
multigrid.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <mpi.h>
4#include <type_traits>
5
6#include "solver.hpp"
7#include "util/table.hpp"
8#include "util/timer.hpp"
9
10namespace terra::linalg::solvers {
11
12/// @brief Placeholder redistribute type — methods never called when
13/// redistribute_down_ is empty. Exists so Multigrid's default template parameter
14/// has a complete, default-constructible type for the no-agglomeration path.
16{
17 template < typename V >
18 void apply( const V&, V& ) const {}
19 template < typename V >
20 void apply_transpose( const V&, V& ) const {}
21};
22
23/// @brief Multigrid solver for linear systems.
24///
25/// Satisfies the SolverLike concept (see solver.hpp).
26/// Supports arbitrary operators, prolongation/restriction, smoothers, and coarse grid solvers.
27/// Implements recursive V-cycle multigrid.
28///
29/// Optionally supports hierarchical agglomeration: per level, a Redistribute
30/// operator moves the restricted residual from the fine level's communicator to
31/// a (smaller) coarse level communicator, and the coarse correction back up.
32/// Inactive coarse ranks skip the recursive descent but still participate in
33/// the collectives inside Redistribute::apply / apply_transpose.
34///
35/// @tparam OperatorT Operator type (must satisfy OperatorLike).
36/// @tparam ProlongationT Prolongation operator type (must satisfy OperatorLike).
37/// @tparam RestrictionT Restriction operator type (must satisfy OperatorLike).
38/// @tparam SmootherT Smoother type (must satisfy SolverLike).
39/// @tparam CoarseGridSolverT Coarse grid solver type (must satisfy SolverLike).
40/// @tparam RedistributeT Optional; defaults to NoRedistribute. Pass the concrete
41/// Redistribute<GridDataType> type when using agglomeration.
42template <
43 OperatorLike OperatorT,
44 OperatorLike ProlongationT,
45 OperatorLike RestrictionT,
46 SolverLike SmootherT,
47 SolverLike CoarseGridSolverT,
48 class RedistributeT = NoRedistribute >
50{
51 public:
52 /// @brief Operator type to be solved.
53 using OperatorType = OperatorT;
54 /// @brief Prolongation operator type.
55 using ProlongationType = ProlongationT;
56 /// @brief Restriction operator type.
57 using RestrictionType = RestrictionT;
58 /// @brief Smoother type.
59 using SmootherType = SmootherT;
60 /// @brief Coarse grid solver type.
61 using CoarseGridSolverType = CoarseGridSolverT;
62
63 /// @brief Solution vector type.
65 /// @brief Right-hand side vector type.
67
68 /// @brief Scalar type for computations.
69 using ScalarType = SolutionVectorType::ScalarType;
70
71 private:
72 std::vector< ProlongationType > P_additive_; ///< Prolongation operators for each level.
73 std::vector< RestrictionType > R_; ///< Restriction operators for each level.
74 std::vector< OperatorT > A_c_; ///< Coarse grid operators for each level.
75 std::vector< SolutionVectorType > tmp_r_; ///< Temporary residual vectors for each level.
76 std::vector< SolutionVectorType > tmp_e_; ///< Temporary error vectors for each level.
77 std::vector< SolutionVectorType > tmp_; ///< Temporary workspace vectors for each level.
78 std::vector< SmootherType > smoothers_pre_; ///< Pre-smoothers for each level.
79 std::vector< SmootherType > smoothers_post_; ///< Post-smoothers for each level.
80 CoarseGridSolverType coarse_grid_solver_; ///< Coarse grid solver.
81
82 int num_cycles_; ///< Number of multigrid cycles to perform.
83 ScalarType relative_residual_threshold_; ///< Relative residual threshold for stopping.
84
85 std::shared_ptr< util::Table > statistics_; ///< Statistics table.
86 std::string tag_ = "multigrid"; ///< Tag for statistics output.
87
88 // ---------------------------------------------------------------------------
89 // Optional agglomeration plumbing. All three vectors are either empty (= no
90 // agglomeration, classical V-cycle) or sized A_c_.size() with one entry per
91 // coarse descent step.
92 //
93 // When configured:
94 // - R_[level-1] writes its output into tmp_r_fine_[level-1] (which lives on
95 // the *fine* level's communicator), and the plan moves that vector's
96 // contents onto tmp_r_[level-1] (which lives on the *coarse* level's
97 // sub-comm). Mirrored for prolongation with tmp_e_fine_.
98 // ---------------------------------------------------------------------------
99 std::vector< RedistributeT > redistribute_down_;
100 std::vector< SolutionVectorType > tmp_r_fine_;
101 std::vector< SolutionVectorType > tmp_e_fine_;
102
103 public:
104 /// @brief Construct a multigrid solver.
105 ///
106 /// Vector ordering of arguments always goes from the coarsest level (index 0) to the finest.
107 ///
108 /// @param P_additive Prolongation operators for each coarse level.
109 /// Size must match the number of levels - 1.
110 /// Must be additive prolongation operators, i.e., @code apply( P, x, y ) @endcode computes \f$ y = y + P x \f$.
111 /// @param R Restriction operators for each coarse level.
112 /// @param A_c Coarse grid operators for each coarse level.
113 /// @param tmp_r Temporary residual vectors for each coarse level.
114 /// @param tmp_e Temporary error vectors for each coarse level.
115 /// @param tmp Temporary workspace vectors for each level (including the finest level).
116 /// @param smoothers_pre Pre-smoothers for each level (including the finest level).
117 /// @param smoothers_post Post-smoothers for each level (including the finest level).
118 /// @param coarse_grid_solver Coarse grid solver.
119 /// @param num_cycles Number of multigrid cycles to perform.
120 /// @param relative_residual_threshold Relative residual threshold for stopping.
122 const std::vector< ProlongationType >& P_additive,
123 const std::vector< RestrictionType >& R,
124 const std::vector< OperatorT >& A_c,
125 const std::vector< SolutionVectorType >& tmp_r,
126 const std::vector< SolutionVectorType >& tmp_e,
127 const std::vector< SolutionVectorType >& tmp,
128 const std::vector< SmootherType >& smoothers_pre,
129 const std::vector< SmootherType >& smoothers_post,
130 const CoarseGridSolverType& coarse_grid_solver,
131 int num_cycles,
132 ScalarType relative_residual_threshold )
133 : P_additive_( P_additive )
134 , R_( R )
135 , A_c_( A_c )
136 , tmp_r_( tmp_r )
137 , tmp_e_( tmp_e )
138 , tmp_( tmp )
139 , smoothers_pre_( smoothers_pre )
140 , smoothers_post_( smoothers_post )
141 , coarse_grid_solver_( coarse_grid_solver )
142 , num_cycles_( num_cycles )
143 , relative_residual_threshold_( relative_residual_threshold )
144 {}
145
146 /// @brief Construct an agglomerated multigrid solver.
147 ///
148 /// Same as the non-agglomerated constructor, plus:
149 /// - @p redistribute_down: Redistribute operators, one per coarse descent
150 /// step. redistribute_down[L-1] moves a vector from level L's comm to
151 /// level L-1's comm. apply_transpose is used on the way back up.
152 /// - @p tmp_r_fine, tmp_e_fine: per-coarse-level scratch vectors allocated
153 /// on the *fine* level's communicator. Restriction output goes into
154 /// tmp_r_fine; prolongation input is read from tmp_e_fine.
155 /// @p tmp_r, tmp_e must carry the coarse level's sub-comm; on ranks not
156 /// part of the sub-comm they are empty (zero-subdomain) vectors.
158 const std::vector< ProlongationType >& P_additive,
159 const std::vector< RestrictionType >& R,
160 const std::vector< OperatorT >& A_c,
161 const std::vector< SolutionVectorType >& tmp_r,
162 const std::vector< SolutionVectorType >& tmp_e,
163 const std::vector< SolutionVectorType >& tmp,
164 const std::vector< SmootherType >& smoothers_pre,
165 const std::vector< SmootherType >& smoothers_post,
166 const CoarseGridSolverType& coarse_grid_solver,
167 int num_cycles,
168 ScalarType relative_residual_threshold,
169 std::vector< RedistributeT > redistribute_down,
170 std::vector< SolutionVectorType > tmp_r_fine,
171 std::vector< SolutionVectorType > tmp_e_fine )
172 : P_additive_( P_additive )
173 , R_( R )
174 , A_c_( A_c )
175 , tmp_r_( tmp_r )
176 , tmp_e_( tmp_e )
177 , tmp_( tmp )
178 , smoothers_pre_( smoothers_pre )
179 , smoothers_post_( smoothers_post )
180 , coarse_grid_solver_( coarse_grid_solver )
181 , num_cycles_( num_cycles )
182 , relative_residual_threshold_( relative_residual_threshold )
183 , redistribute_down_( std::move( redistribute_down ) )
184 , tmp_r_fine_( std::move( tmp_r_fine ) )
185 , tmp_e_fine_( std::move( tmp_e_fine ) )
186 {
187 // Allow two modes for the three agglomeration vectors:
188 // all empty = agglomeration disabled at runtime
189 // (solve_recursive's `agglomerated = !redistribute_down_.empty()` sees false);
190 // all sized A_c = agglomeration enabled, one entry per descent.
191 const bool all_empty = redistribute_down_.empty() && tmp_r_fine_.empty() && tmp_e_fine_.empty();
192 const bool all_sized = redistribute_down_.size() == A_c.size()
193 && tmp_r_fine_.size() == A_c.size()
194 && tmp_e_fine_.size() == A_c.size();
195 if ( !all_empty && !all_sized )
196 {
197 throw std::runtime_error(
198 "Multigrid (agglomerated): redistribute_down, tmp_r_fine, and tmp_e_fine must either all be empty "
199 "(agglomeration disabled) or each have one entry per coarse level." );
200 }
201 }
202
203 /// @brief Set a tag string for statistics output.
204 /// @param tag Tag string.
205 void set_tag( const std::string& tag ) { tag_ = tag; }
206
207 /// @brief Collect statistics in a shared table.
208 /// @param statistics Shared pointer to statistics table.
209 void collect_statistics( const std::shared_ptr< util::Table >& statistics ) { statistics_ = statistics; }
210
211 /// @brief Solve the linear system using multigrid cycles.
212 /// Calls the recursive V-cycle and updates statistics.
213 /// @param A Operator (matrix).
214 /// @param x Solution vector (output).
215 /// @param b Right-hand side vector (input).
217 {
218 util::Timer timer_mg_solve( "mg_solve" );
219
220 if ( P_additive_.size() != A_c_.size() || R_.size() != A_c_.size() || tmp_r_.size() != A_c_.size() ||
221 tmp_e_.size() != A_c_.size() || tmp_.size() != A_c_.size() + 1 )
222 {
223 throw std::runtime_error(
224 "Multigrid: P_additive, R, A_c, tmp_e, and tmp_r must be available for all coarse levels. tmp "
225 "requires the finest grid allocated, too." );
226 }
227
228 const int max_level = P_additive_.size();
229
230 ScalarType initial_residual = 0.0;
231
232 if ( statistics_ )
233 {
234 util::Timer t_res( "mg_residual_compute" );
235 apply( A, x, tmp_[max_level] );
236 lincomb( tmp_[max_level], { 1.0, -1.0 }, { b, tmp_[max_level] } );
237 initial_residual = norm_2( tmp_[max_level] );
238
239 statistics_->add_row(
240 { { "tag", tag_ },
241 { "cycle", 0 },
242 { "relative_residual", 1.0 },
243 { "absolute_residual", initial_residual },
244 { "residual_convergence_rate", 1.0 } } );
245 }
246
247 ScalarType previous_absolut_residual = initial_residual;
248
249 for ( int cycle = 1; cycle <= num_cycles_; ++cycle )
250 {
251 {
252 util::Timer t_cyc( "mg_vcycle" );
253 solve_recursive( A, x, b, max_level );
254 }
255
256 if ( statistics_ )
257 {
258 util::Timer t_res( "mg_residual_compute" );
259 apply( A, x, tmp_[max_level] );
260 lincomb( tmp_[max_level], { 1.0, -1.0 }, { b, tmp_[max_level] } );
261 const auto absolute_residual = norm_2( tmp_[max_level] );
262
263 const auto relative_residual = absolute_residual / initial_residual;
264
265 statistics_->add_row(
266 { { "tag", tag_ },
267 { "cycle", cycle },
268 { "relative_residual", relative_residual },
269 { "absolute_residual", absolute_residual },
270 { "residual_convergence_rate", absolute_residual / previous_absolut_residual } } );
271
272 if ( relative_residual <= relative_residual_threshold_ )
273 {
274 return;
275 }
276
277 previous_absolut_residual = absolute_residual;
278 }
279 }
280 }
281
282 private:
283 /// @brief Recursive V-cycle multigrid solver.
284 /// @param A Operator (matrix) at current level.
285 /// @param x Solution vector (output) at current level.
286 /// @param b Right-hand side vector (input) at current level.
287 /// @param level Current multigrid level.
288 void solve_recursive( OperatorType& A, SolutionVectorType& x, const RHSVectorType& b, int level )
289 {
290 if ( level == 0 )
291 {
292 util::Timer t_cs( "mg_coarse_solve" );
293 solve( coarse_grid_solver_, A, tmp_e_[0], tmp_r_[0] );
294 return;
295 }
296
297 const std::string level_suffix = "_L" + std::to_string( level );
298
299 // Agglomeration is only available when RedistributeT is a concrete
300 // redistribute type (not the NoRedistribute placeholder) AND the caller
301 // provided the per-level operators/buffers. The if constexpr below
302 // ensures the agglomerated branches — which require .grid_data() on the
303 // vector type and valid per-level buffers — aren't instantiated when
304 // RedistributeT is NoRedistribute.
305 constexpr bool can_agglomerate = !std::is_same_v< RedistributeT, NoRedistribute >;
306
307 // relax on Ax = b
308 {
309 util::Timer t_sp( "mg_smoother_pre" + level_suffix );
310 solve( smoothers_pre_[level], A, x, b );
311 }
312
313 bool active_at_coarse = true;
314
315#ifdef TERRA_MG_AGGLOM_DEBUG
316 auto dbg = [&]( const char* phase ) {
317 int r; MPI_Comm_rank( MPI_COMM_WORLD, &r );
318 if ( r == 0 )
319 std::cout << "[MG_DBG] level=" << level << " phase=" << phase << std::endl << std::flush;
320 };
321#else
322 auto dbg = []( const char* ) {};
323#endif
324
325 if constexpr ( can_agglomerate )
326 {
327 // Runtime-selected path. When redistribute_down_ is empty this
328 // collapses to the classical V-cycle (no agglomeration), which lets
329 // callers instantiate Multigrid with a real Redistribute type but
330 // leave the extra vectors empty to disable agglomeration at runtime.
331 if ( redistribute_down_.empty() )
332 {
333 // Classical V-cycle.
334 {
335 util::Timer t_rr( "mg_residual_and_restrict" + level_suffix );
336 apply( A, x, tmp_[level] );
337 lincomb( tmp_[level], { 1.0, -1.0 }, { b, tmp_[level] } );
338 apply( R_[level - 1], tmp_[level], tmp_r_[level - 1] );
339 }
340
341 assign( tmp_e_[level - 1], 0.0 );
342 solve_recursive( A_c_[level - 1], tmp_e_[level - 1], tmp_r_[level - 1], level - 1 );
343
344 {
345 util::Timer t_p( "mg_prolongate_correct" + level_suffix );
346 apply( P_additive_[level - 1], tmp_e_[level - 1], x );
347 }
348 }
349 else
350 {
351 // Agglomerated path. If this descent's Redistribute is the
352 // identity (factor=1: upper and lower comms + owner maps match
353 // exactly), there's no hop to do — treat it as a classical
354 // descent: restrict straight into tmp_r_[L-1], no apply, no
355 // apply_transpose, prolongate straight from tmp_e_[L-1].
356 const bool identity_descent = redistribute_down_[level - 1].is_identity();
357
358 dbg( "pre_smooth_done" );
359 {
360 util::Timer t_rr( "mg_residual_and_restrict" + level_suffix );
361 apply( A, x, tmp_[level] );
362 dbg( "residual_applied" );
363 lincomb( tmp_[level], { 1.0, -1.0 }, { b, tmp_[level] } );
364 apply( R_[level - 1],
365 tmp_[level],
366 identity_descent ? tmp_r_[level - 1] : tmp_r_fine_[level - 1] );
367 dbg( "restriction_done" );
368 }
369
370 if ( !identity_descent )
371 {
372 util::Timer t_rd( "mg_redistribute_down" + level_suffix );
373 redistribute_down_[level - 1].apply( tmp_r_fine_[level - 1].grid_data(),
374 tmp_r_[level - 1].grid_data() );
375 dbg( "redistribute_down_done" );
376 }
377 active_at_coarse = ( tmp_r_[level - 1].comm() != MPI_COMM_NULL );
378
379 if ( active_at_coarse )
380 {
381 assign( tmp_e_[level - 1], 0.0 );
382 dbg( "entering_coarse_recursion" );
383 solve_recursive( A_c_[level - 1], tmp_e_[level - 1], tmp_r_[level - 1], level - 1 );
384 dbg( "returned_from_coarse" );
385 }
386 else
387 {
388 dbg( "skipped_coarse_recursion" );
389 }
390
391 if ( !identity_descent )
392 {
393 util::Timer t_ru( "mg_redistribute_up" + level_suffix );
394 redistribute_down_[level - 1].apply_transpose( tmp_e_[level - 1].grid_data(),
395 tmp_e_fine_[level - 1].grid_data() );
396 dbg( "redistribute_up_done" );
397 }
398
399 {
400 util::Timer t_p( "mg_prolongate_correct" + level_suffix );
401 apply( P_additive_[level - 1],
402 identity_descent ? tmp_e_[level - 1] : tmp_e_fine_[level - 1],
403 x );
404 dbg( "prolongation_done" );
405 }
406 }
407 }
408 else
409 {
410 // Classical V-cycle path.
411 {
412 util::Timer t_rr( "mg_residual_and_restrict" + level_suffix );
413 apply( A, x, tmp_[level] );
414 lincomb( tmp_[level], { 1.0, -1.0 }, { b, tmp_[level] } );
415 apply( R_[level - 1], tmp_[level], tmp_r_[level - 1] );
416 }
417
418 assign( tmp_e_[level - 1], 0.0 );
419 solve_recursive( A_c_[level - 1], tmp_e_[level - 1], tmp_r_[level - 1], level - 1 );
420
421 {
422 util::Timer t_p( "mg_prolongate_correct" + level_suffix );
423 apply( P_additive_[level - 1], tmp_e_[level - 1], x );
424 }
425 }
426
427 // relax on A x = b
428 {
429 util::Timer t_sP( "mg_smoother_post" + level_suffix );
430 solve( smoothers_post_[level], A, x, b );
431 }
432 }
433};
434
435} // namespace terra::linalg::solvers
Multigrid solver for linear systems.
Definition multigrid.hpp:50
CoarseGridSolverT CoarseGridSolverType
Coarse grid solver type.
Definition multigrid.hpp:61
OperatorT OperatorType
Operator type to be solved.
Definition multigrid.hpp:53
void solve_impl(OperatorType &A, SolutionVectorType &x, const RHSVectorType &b)
Solve the linear system using multigrid cycles. Calls the recursive V-cycle and updates statistics.
Definition multigrid.hpp:216
DstOf< OperatorType > RHSVectorType
Right-hand side vector type.
Definition multigrid.hpp:66
void set_tag(const std::string &tag)
Set a tag string for statistics output.
Definition multigrid.hpp:205
SrcOf< OperatorType > SolutionVectorType
Solution vector type.
Definition multigrid.hpp:64
Multigrid(const std::vector< ProlongationType > &P_additive, const std::vector< RestrictionType > &R, const std::vector< OperatorT > &A_c, const std::vector< SolutionVectorType > &tmp_r, const std::vector< SolutionVectorType > &tmp_e, const std::vector< SolutionVectorType > &tmp, const std::vector< SmootherType > &smoothers_pre, const std::vector< SmootherType > &smoothers_post, const CoarseGridSolverType &coarse_grid_solver, int num_cycles, ScalarType relative_residual_threshold, std::vector< RedistributeT > redistribute_down, std::vector< SolutionVectorType > tmp_r_fine, std::vector< SolutionVectorType > tmp_e_fine)
Construct an agglomerated multigrid solver.
Definition multigrid.hpp:157
SmootherT SmootherType
Smoother type.
Definition multigrid.hpp:59
SolutionVectorType::ScalarType ScalarType
Scalar type for computations.
Definition multigrid.hpp:69
Multigrid(const std::vector< ProlongationType > &P_additive, const std::vector< RestrictionType > &R, const std::vector< OperatorT > &A_c, const std::vector< SolutionVectorType > &tmp_r, const std::vector< SolutionVectorType > &tmp_e, const std::vector< SolutionVectorType > &tmp, const std::vector< SmootherType > &smoothers_pre, const std::vector< SmootherType > &smoothers_post, const CoarseGridSolverType &coarse_grid_solver, int num_cycles, ScalarType relative_residual_threshold)
Construct a multigrid solver.
Definition multigrid.hpp:121
RestrictionT RestrictionType
Restriction operator type.
Definition multigrid.hpp:57
ProlongationT ProlongationType
Prolongation operator type.
Definition multigrid.hpp:55
void collect_statistics(const std::shared_ptr< util::Table > &statistics)
Collect statistics in a shared table.
Definition multigrid.hpp:209
Timer supporting RAII scope or manual stop.
Definition timer.hpp:342
Concept for types that behave like linear operators.
Definition operator.hpp:57
Definition block_preconditioner_2x2.hpp:7
void solve(Solver &solver, Operator &A, SolutionVector &x, const RHSVector &b)
Solve a linear system using the given solver and operator. Calls the solver's solve_impl method.
Definition solver.hpp:51
void lincomb(Vector &y, const std::vector< ScalarOf< Vector > > &c, const std::vector< Vector > &x, const ScalarOf< Vector > &c0)
Compute a linear combination of vectors. Implements: .
Definition vector.hpp:72
ScalarOf< Vector > norm_2(const Vector &y)
Compute the 2-norm (Euclidean norm) of a vector. Implements: .
Definition vector.hpp:166
Operator::SrcVectorType SrcOf
Alias for the source vector type of an operator.
Definition operator.hpp:145
void apply(LinearForm &L, typename LinearForm::DstVectorType &dst)
Apply a linear form and write to a destination vector.
Definition linear_form.hpp:37
Operator::DstVectorType DstOf
Alias for the destination vector type of an operator.
Definition operator.hpp:149
void assign(Vector &y, const ScalarOf< Vector > &c0)
Assign a scalar value to a vector. Implements: .
Definition vector.hpp:97
Placeholder redistribute type — methods never called when redistribute_down_ is empty....
Definition multigrid.hpp:16
void apply(const V &, V &) const
Definition multigrid.hpp:18
void apply_transpose(const V &, V &) const
Definition multigrid.hpp:20