Skip to content

Commit 18d7d76

Browse files
authored
Add Preconditioned Conjugate Gradient Method (#188)
1 parent aac9b10 commit 18d7d76

File tree

17 files changed

+1374
-118
lines changed

17 files changed

+1374
-118
lines changed

include/ConfigParser/config_parser.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,20 @@ class ConfigParser
4242

4343
const PolarGrid& grid() const;
4444

45-
// Multigrid Parameters
45+
// Full Multigrid Method
4646
bool FMG() const;
4747
int FMG_iterations() const;
4848
MultigridCycleType FMG_cycle() const;
49+
50+
// Preconditioned Conjugate Gradient Method
51+
bool PCG() const;
52+
bool PCG_FMG() const;
53+
int PCG_FMG_iterations() const;
54+
MultigridCycleType PCG_FMG_cycle() const;
55+
int PCG_MG_iterations() const;
56+
MultigridCycleType PCG_MG_cycle() const;
57+
58+
// Multigrid Parameters
4959
ExtrapolationType extrapolation() const;
5060
int maxLevels() const;
5161
int preSmoothingSteps() const;
@@ -86,6 +96,12 @@ class ConfigParser
8696
bool FMG_;
8797
int FMG_iterations_;
8898
MultigridCycleType FMG_cycle_;
99+
bool PCG_;
100+
bool PCG_FMG_;
101+
int PCG_FMG_iterations_;
102+
MultigridCycleType PCG_FMG_cycle_;
103+
int PCG_MG_iterations_;
104+
MultigridCycleType PCG_MG_cycle_;
89105
// Iterative solver controls
90106
int max_iterations_;
91107
ResidualNormType residual_norm_type_;

include/GMGPolar/igmgpolar.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@ class IGMGPolar
105105
MultigridCycleType FMG_cycle() const;
106106
void FMG_cycle(MultigridCycleType FMG_cycle);
107107

108+
// Preconditioned Conjugate Gradient (PCG) control.
109+
bool PCG() const;
110+
void PCG(bool PCG);
111+
bool PCG_FMG() const;
112+
void PCG_FMG(bool PCG_FMG);
113+
int PCG_FMG_iterations() const;
114+
void PCG_FMG_iterations(int PCG_FMG_iterations);
115+
MultigridCycleType PCG_FMG_cycle() const;
116+
void PCG_FMG_cycle(MultigridCycleType PCG_FMG_cycle);
117+
int PCG_MG_iterations() const;
118+
void PCG_MG_iterations(int PCG_MG_iterations);
119+
MultigridCycleType PCG_MG_cycle() const;
120+
void PCG_MG_cycle(MultigridCycleType PCG_MG_cycle);
121+
108122
/* ---------------------------------------------------------------------- */
109123
/* Iterative solver termination */
110124
/* ---------------------------------------------------------------------- */
@@ -178,6 +192,7 @@ class IGMGPolar
178192
double timeSolveMultigridIterations() const;
179193
double timeCheckConvergence() const;
180194
double timeCheckExactError() const;
195+
double timeConjugateGradient() const;
181196

182197
double timeAvgMGCTotal() const;
183198
double timeAvgMGCPreSmoothing() const;
@@ -215,6 +230,13 @@ class IGMGPolar
215230
bool FMG_;
216231
int FMG_iterations_;
217232
MultigridCycleType FMG_cycle_;
233+
// PCG settings
234+
bool PCG_;
235+
bool PCG_FMG_;
236+
int PCG_FMG_iterations_;
237+
MultigridCycleType PCG_FMG_cycle_;
238+
int PCG_MG_iterations_;
239+
MultigridCycleType PCG_MG_cycle_;
218240
// Convergence settings
219241
int max_iterations_;
220242
ResidualNormType residual_norm_type_;
@@ -234,6 +256,22 @@ class IGMGPolar
234256
/* Chooses if full grid smoothing is active on level 0 for extrapolation > 0 */
235257
bool full_grid_smoothing_ = false;
236258

259+
/* -------------------------------------------------- */
260+
/* Vectors for PCG (Preconditioned Conjugate Gradient)
261+
* https://en.wikipedia.org/wiki/Conjugate_gradient_method#The_preconditioned_conjugate_gradient_method
262+
*
263+
* Dedicated vectors:
264+
* x (solution) -> pcg_solution_
265+
* p (search direction) -> pcg_search_direction_
266+
*
267+
* Reused vectors (to avoid extra allocations):
268+
* r (residual) -> level.rhs()
269+
* z (preconditioned residual) -> level.solution()
270+
* A*p (matrix applied to search dir.) -> level.residual()
271+
*/
272+
AllocatableVector<double> pcg_solution_; // x (solution)
273+
AllocatableVector<double> pcg_search_direction_; // p (search direction)
274+
237275
/* -------------------- */
238276
/* Convergence criteria */
239277
int number_of_iterations_;
@@ -263,10 +301,16 @@ class IGMGPolar
263301
/* --------------- */
264302
/* Solve Functions */
265303
void fullMultigridApproximation(MultigridCycleType FMG_cycle, int FMG_iterations);
304+
void solveMultigrid(double& initial_residual_norm, double& current_residual_norm,
305+
double& current_relative_residual_norm);
306+
void solvePCG(double& initial_residual_norm, double& current_residual_norm, double& current_relative_residual_norm);
266307
double residualNorm(const ResidualNormType& norm_type, const Level& level, ConstVector<double> residual) const;
267308
void evaluateExactError(Level& level, const ExactSolution& exact_solution);
268309
void updateResidualNorms(Level& level, int iteration, double& initial_residual_norm, double& current_residual_norm,
269310
double& current_relative_residual_norm);
311+
void initRhsHierarchy(Vector<double> rhs);
312+
void applyMultigridIterations(Level& level, MultigridCycleType cycle, int iterations);
313+
void applyExtrapolatedMultigridIterations(Level& level, MultigridCycleType cycle, int iterations);
270314

271315
/* ----------------- */
272316
/* Print information */
@@ -319,6 +363,7 @@ class IGMGPolar
319363
double t_solve_multigrid_iterations_;
320364
double t_check_convergence_;
321365
double t_check_exact_error_;
366+
double t_conjugate_gradient_;
322367

323368
void resetAvgMultigridCycleTimings();
324369
double t_avg_MGC_total_;

include/GMGPolar/setup.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,15 @@ void GMGPolar<DomainGeometry, DensityProfileCoefficients>::setup()
5656

5757
auto finest_levelCache = std::make_unique<LevelCache>(*finest_grid, density_profile_coefficients_, domain_geometry_,
5858
cache_density_profile_coefficients_, cache_domain_geometry_);
59-
levels_.emplace_back(0, std::move(finest_grid), std::move(finest_levelCache), extrapolation_, FMG_);
59+
levels_.emplace_back(0, std::move(finest_grid), std::move(finest_levelCache), extrapolation_, FMG_, PCG_FMG_);
6060

6161
for (int level_depth = 1; level_depth < number_of_levels_; level_depth++) {
6262
auto current_grid = std::make_unique<PolarGrid>(coarseningGrid(levels_[level_depth - 1].grid()));
6363
auto current_levelCache =
6464
std::make_unique<LevelCache>(*current_grid, density_profile_coefficients_, domain_geometry_,
6565
cache_density_profile_coefficients_, cache_domain_geometry_);
66-
levels_.emplace_back(level_depth, std::move(current_grid), std::move(current_levelCache), extrapolation_, FMG_);
66+
levels_.emplace_back(level_depth, std::move(current_grid), std::move(current_levelCache), extrapolation_, FMG_,
67+
PCG_FMG_);
6768
}
6869

6970
auto end_setup_createLevels = std::chrono::high_resolution_clock::now();
@@ -80,6 +81,19 @@ void GMGPolar<DomainGeometry, DensityProfileCoefficients>::setup()
8081
if (verbose_ > 0)
8182
printSettings();
8283

84+
// ------------------------------- //
85+
// PCG-specific vector allocations //
86+
// ------------------------------- //
87+
if (PCG_) {
88+
const int grid_size = levels_[0].grid().numberOfNodes();
89+
if (std::ssize(pcg_solution_) != grid_size) {
90+
pcg_solution_ = Vector<double>("pcg_solution", grid_size);
91+
}
92+
if (std::ssize(pcg_search_direction_) != grid_size) {
93+
pcg_search_direction_ = Vector<double>("pcg_search_direction", grid_size);
94+
}
95+
}
96+
8397
// ------------------------------------------------ //
8498
// Define residual operator on all multigrid levels //
8599
// ------------------------------------------------ //
@@ -129,11 +143,13 @@ void GMGPolar<DomainGeometry, DensityProfileCoefficients>::setup()
129143
full_grid_smoothing_ = do_full_grid_smoothing;
130144

131145
if (number_of_levels_ > 1) {
132-
if (do_full_grid_smoothing) {
146+
// PCG uses non-extrapolated smoothing on level 0, so we need to initialize it if PCG is enabled.
147+
if (do_full_grid_smoothing || (PCG_ && PCG_MG_iterations_ > 0)) {
133148
levels_[0].initializeSmoothing(domain_geometry_, density_profile_coefficients_, DirBC_Interior_,
134149
max_omp_threads_, stencil_distribution_method_);
135150
}
136-
if (do_extrapolated_smoothing) {
151+
// PCG doesn't use extrapolated smoothing, so we only initialize it if PCG is disabled.
152+
if (do_extrapolated_smoothing && !PCG_) {
137153
levels_[0].initializeExtrapolatedSmoothing(domain_geometry_, density_profile_coefficients_, DirBC_Interior_,
138154
max_omp_threads_, stencil_distribution_method_);
139155
}

include/GMGPolar/solver.h

Lines changed: 42 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -86,77 +86,51 @@ void IGMGPolar::solve(const BoundaryConditions& boundary_conditions, const Sourc
8686

8787
printIterationHeader(exact_solution_);
8888

89-
while (number_of_iterations_ < max_iterations_) {
90-
/* ---------------------------------------------- */
91-
/* Test solution against exact solution if given. */
92-
/* ---------------------------------------------- */
93-
LIKWID_STOP("Solver");
94-
auto start_check_exact_error = std::chrono::high_resolution_clock::now();
95-
96-
if (exact_solution_ != nullptr)
97-
evaluateExactError(level, *exact_solution_);
98-
99-
auto end_check_exact_error = std::chrono::high_resolution_clock::now();
100-
t_check_exact_error_ += std::chrono::duration<double>(end_check_exact_error - start_check_exact_error).count();
101-
LIKWID_START("Solver");
102-
103-
/* ---------------------------- */
104-
/* Compute convergence criteria */
105-
/* ---------------------------- */
106-
auto start_check_convergence = std::chrono::high_resolution_clock::now();
107-
108-
if (absolute_tolerance_.has_value() || relative_tolerance_.has_value()) {
109-
updateResidualNorms(level, number_of_iterations_, initial_residual_norm, current_residual_norm,
110-
current_relative_residual_norm);
111-
}
89+
/* ---------------------------------------------- */
90+
/* Test solution against exact solution if given. */
91+
/* ---------------------------------------------- */
92+
LIKWID_STOP("Solver");
93+
auto start_check_exact_error = std::chrono::high_resolution_clock::now();
94+
95+
if (exact_solution_ != nullptr)
96+
evaluateExactError(level, *exact_solution_);
97+
98+
auto end_check_exact_error = std::chrono::high_resolution_clock::now();
99+
t_check_exact_error_ += std::chrono::duration<double>(end_check_exact_error - start_check_exact_error).count();
100+
LIKWID_START("Solver");
101+
102+
/* ---------------------------- */
103+
/* Compute convergence criteria */
104+
/* ---------------------------- */
105+
auto start_check_convergence = std::chrono::high_resolution_clock::now();
112106

113-
auto end_check_convergence = std::chrono::high_resolution_clock::now();
114-
t_check_convergence_ += std::chrono::duration<double>(end_check_convergence - start_check_convergence).count();
115-
116-
printIterationInfo(number_of_iterations_, current_residual_norm, current_relative_residual_norm,
117-
exact_solution_);
118-
119-
if (converged(current_residual_norm, current_relative_residual_norm))
120-
break;
121-
122-
/* ----------------------- */
123-
/* Perform Multigrid Cycle */
124-
/* ----------------------- */
125-
auto start_solve_multigrid_iterations = std::chrono::high_resolution_clock::now();
126-
127-
switch (multigrid_cycle_) {
128-
case MultigridCycleType::V_CYCLE:
129-
if (extrapolation_ == ExtrapolationType::NONE) {
130-
multigrid_V_Cycle(level.level_depth(), level.solution(), level.rhs(), level.residual());
131-
}
132-
else {
133-
extrapolated_multigrid_V_Cycle(level.level_depth(), level.solution(), level.rhs(), level.residual());
134-
}
135-
break;
136-
case MultigridCycleType::W_CYCLE:
137-
if (extrapolation_ == ExtrapolationType::NONE) {
138-
multigrid_W_Cycle(level.level_depth(), level.solution(), level.rhs(), level.residual());
139-
}
140-
else {
141-
extrapolated_multigrid_W_Cycle(level.level_depth(), level.solution(), level.rhs(), level.residual());
142-
}
143-
break;
144-
case MultigridCycleType::F_CYCLE:
145-
if (extrapolation_ == ExtrapolationType::NONE) {
146-
multigrid_F_Cycle(level.level_depth(), level.solution(), level.rhs(), level.residual());
147-
}
148-
else {
149-
extrapolated_multigrid_F_Cycle(level.level_depth(), level.solution(), level.rhs(), level.residual());
150-
}
151-
break;
152-
default:
153-
throw std::invalid_argument("Unknown MultigridCycleType");
107+
// Initializes level.residual() and sets up the convergence criteria.
108+
updateResidualNorms(level, number_of_iterations_, initial_residual_norm, current_residual_norm,
109+
current_relative_residual_norm);
110+
111+
auto end_check_convergence = std::chrono::high_resolution_clock::now();
112+
t_check_convergence_ += std::chrono::duration<double>(end_check_convergence - start_check_convergence).count();
113+
114+
printIterationInfo(number_of_iterations_, current_residual_norm, current_relative_residual_norm, exact_solution_);
115+
116+
if (!converged(current_residual_norm, current_relative_residual_norm)) {
117+
if (!PCG_) {
118+
// Solve A*x = b directly using multigrid cycles (V/W/F-cycle)
119+
// until convergence or max_iterations_ is reached.
120+
solveMultigrid(initial_residual_norm, current_residual_norm, current_relative_residual_norm);
154121
}
155-
number_of_iterations_++;
122+
else {
123+
// Solve A*x = b using Preconditioned Conjugate Gradient (PCG),
124+
// with multigrid cycles as the preconditioner (i.e., one multigrid
125+
// cycle approximates the action of A^{-1} at each PCG iteration).
126+
auto start_conjugate_gradient = std::chrono::high_resolution_clock::now();
156127

157-
auto end_solve_multigrid_iterations = std::chrono::high_resolution_clock::now();
158-
t_solve_multigrid_iterations_ +=
159-
std::chrono::duration<double>(end_solve_multigrid_iterations - start_solve_multigrid_iterations).count();
128+
solvePCG(initial_residual_norm, current_residual_norm, current_relative_residual_norm);
129+
130+
auto end_conjugate_gradient = std::chrono::high_resolution_clock::now();
131+
t_conjugate_gradient_ +=
132+
std::chrono::duration<double>(end_conjugate_gradient - start_conjugate_gradient).count();
133+
}
160134
}
161135

162136
/* ---------------------- */

include/Level/level.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ class Level
4343
// ----------- //
4444
// Constructor //
4545
explicit Level(const int level_depth, std::unique_ptr<const PolarGrid> grid,
46-
std::unique_ptr<const LevelCache> level_cache, const ExtrapolationType extrapolation,
47-
const bool FMG);
46+
std::unique_ptr<const LevelCache> level_cache, const ExtrapolationType extrapolation, const bool FMG,
47+
const bool PCG_FMG = false);
4848

4949
// ---------------- //
5050
// Getter Functions //
@@ -67,6 +67,7 @@ class Level
6767
const DensityProfileCoefficients& density_profile_coefficients, const bool DirBC_Interior,
6868
const int num_omp_threads, const StencilDistributionMethod stencil_distribution_method);
6969
void computeResidual(Vector<double> result, ConstVector<double> rhs, ConstVector<double> x) const;
70+
void applySystemOperator(Vector<double> result, ConstVector<double> x) const;
7071

7172
// ------------------- //
7273
// Solve coarse System //

scripts/compile.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,4 @@ if [ -n "$build_type" ]; then
5959
-DCMAKE_BUILD_TYPE="$build_type" .. || { echo "CMake configuration failed"; exit 1; }
6060
fi
6161

62-
cmake --build ${PWD}/../build -j 16
62+
cmake --build ${PWD}/../build -j 2

scripts/tutorial/run.sh

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ paraview=0
1616

1717
# OpenMP settings:
1818
# Maximum number of threads OpenMP can use for parallel execution
19-
maxOpenMPThreads=32
19+
maxOpenMPThreads=1
2020

2121
# Stencil distribution method:
2222
# 0 - CPU "Take": Each node independently applies the stencil
2323
# 1 - CPU "Give": The stencil operation is distributed across adjacent neighboring nodes
24-
stencilDistributionMethod=1
24+
stencilDistributionMethod=0
2525
# Caching behavior:
2626
# 0 - Recompute values on each iteration: Uses less memory but results in slower execution.
2727
# 1 - Reuse cached values: Consumes more memory but significantly improves performance.
2828
cacheDensityProfileCoefficients=1
29-
cacheDomainGeometry=0
29+
cacheDomainGeometry=1
3030
# Note: In the "Take" approach (stencilDistributionMethod=0),
3131
# caching is required for optimal performance,
3232
# so both density profile coefficients and domain geometry need to be cached.
@@ -62,10 +62,24 @@ beta_coeff=1 # Zero(0), Gyro - Alpha Inverse(1)
6262
# Full Multigrid Method:
6363
# 0: Initial approximation is set to zero
6464
# 1: Initial approximation obtained by nested iteration (recommended)
65-
FMG=0
66-
FMG_iterations=3
65+
FMG=1
66+
FMG_iterations=2
6767
FMG_cycle=2 # V-Cycle(0), W-Cycle(1), F-Cycle(2)
6868

69+
# Preconditioned Conjugate Gradient Method:
70+
# 0: GMGPolar as iterative solver
71+
# 1: GMGPolar solver as preconditioner for Conjugate Gradient (recommended)
72+
PCG=1
73+
# Initial approximation for PCG:
74+
# 0: Initial approximation is set to residual -> no preconditioning
75+
# 1: FMG-approximation as initial guess (recommended)
76+
PCG_FMG=1
77+
PCG_FMG_iterations=1
78+
PCG_FMG_cycle=0 # V-Cycle(0), W-Cycle(1), F-Cycle(2)
79+
# Additional multigrid iterations after initial approximation to solve the linear system in each PCG iteration
80+
PCG_MG_iterations=1
81+
PCG_MG_cycle=0 # V-Cycle(0), W-Cycle(1), F-Cycle(2)
82+
6983
# Extrapolation Method:
7084
# 0: No extrapolation
7185
# 1: Implicit extrapolation (recommended)
@@ -86,8 +100,8 @@ multigridCycle=0
86100
# Convergence criteria:
87101
maxIterations=150
88102
residualNormType=0 # L2-Norm(0) = 0, Weighted L2-Norm(1), Infinity-Norm(2)
89-
absoluteTolerance=1e-8
90-
relativeTolerance=1e-8
103+
absoluteTolerance=1e-10
104+
relativeTolerance=1e-10
91105

92106
# Define additional geometry parameters
93107
kappa_eps=0.0
@@ -141,6 +155,12 @@ export OMP_NUM_THREADS=$maxOpenMPThreads
141155
--FMG $FMG \
142156
--FMG_iterations $FMG_iterations \
143157
--FMG_cycle $FMG_cycle \
158+
--PCG $PCG \
159+
--PCG_FMG $PCG_FMG \
160+
--PCG_FMG_iterations $PCG_FMG_iterations \
161+
--PCG_FMG_cycle $PCG_FMG_cycle \
162+
--PCG_MG_iterations $PCG_MG_iterations \
163+
--PCG_MG_cycle $PCG_MG_cycle \
144164
--extrapolation $extrapolation \
145165
--maxLevels $maxLevels \
146166
--preSmoothingSteps $preSmoothingSteps \

0 commit comments

Comments
 (0)