Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
block_preconditioner.cpp
1 #include "smith/numerics/block_preconditioner.hpp"
2 
3 #include <memory>
4 #include <utility>
5 #include <vector>
6 #include <stdexcept>
7 
8 #include "mfem.hpp"
9 #include "axom/slic/core/SimpleLogger.hpp"
10 
11 namespace smith {
12 
13 namespace {
14 
15 void applyOverrides(int num_blocks, std::vector<std::unique_ptr<const mfem::Operator>>& block_op_overrides,
16  std::vector<BlockOverride> overrides)
17 {
18  for (auto& ov : overrides) {
19  const int i = ov.first;
20  auto& op = ov.second;
21 
22  if (i < 0 || i >= num_blocks) {
23  throw std::out_of_range("Override block index out of range");
24  }
25  if (!op) {
26  throw std::invalid_argument("Override operator must be non-null");
27  }
28  if (block_op_overrides[static_cast<size_t>(i)]) {
29  throw std::invalid_argument("Duplicate override for same block index");
30  }
31 
32  block_op_overrides[static_cast<size_t>(i)] = std::move(op);
33  }
34 }
35 
36 } // namespace
37 
39  std::vector<std::unique_ptr<mfem::Solver>> solvers,
40  std::vector<BlockOverride> overrides)
41  : block_offsets_(offsets),
42  num_blocks_(offsets.Size() - 1),
43  block_jacobian_(nullptr),
44  solver_diag_(block_offsets_),
45  mfem_solvers_(std::move(solvers)),
46  block_op_overrides_(static_cast<size_t>(num_blocks_))
47 {
48  SLIC_ERROR_IF(mfem_solvers_.size() != static_cast<size_t>(num_blocks_),
49  "Number of solvers must match number of blocks");
50 
51  applyOverrides(num_blocks_, block_op_overrides_, std::move(overrides));
52 }
53 
54 void BlockDiagonalPreconditioner::Mult(const mfem::Vector& in, mfem::Vector& out) const { solver_diag_.Mult(in, out); }
55 
56 void BlockDiagonalPreconditioner::SetOperator(const mfem::Operator& jacobian)
57 {
58  height = jacobian.Height();
59  width = jacobian.Width();
60  // Cast the supplied jacobian to a block operator object
61  block_jacobian_ = dynamic_cast<const mfem::BlockOperator*>(&jacobian);
62 
63  // For each diagonal block A_ii, configure the corresponding solver
64  for (int i = 0; i < num_blocks_; i++) {
65  // Attach operator to solver
66  const mfem::Operator* op = nullptr;
67  const size_t si = static_cast<size_t>(i);
68 
69  if (block_op_overrides_[si]) {
70  op = block_op_overrides_[si].get(); // use override
71  } else {
72  op = &block_jacobian_->GetBlock(i, i); // use Jacobian diagonal block
73  }
74 
75  mfem_solvers_[si]->SetOperator(*op);
76 
77  // Place the solver into the diagonal block of solver_diag_
78  solver_diag_.SetBlock(i, i, mfem_solvers_[si].get());
79  }
80 }
81 
82 BlockDiagonalPreconditioner::~BlockDiagonalPreconditioner() {}
83 
85  std::vector<std::unique_ptr<mfem::Solver>> solvers,
87  std::vector<BlockOverride> overrides)
88  : block_offsets_(offsets),
89  num_blocks_(offsets.Size() - 1),
90  block_jacobian_(nullptr),
91  mfem_solvers_(std::move(solvers)),
92  type_(type),
93  block_op_overrides_(static_cast<size_t>(num_blocks_))
94 {
95  SLIC_ERROR_IF(mfem_solvers_.size() != static_cast<size_t>(num_blocks_),
96  "Number of solvers must match number of blocks");
97 
98  applyOverrides(num_blocks_, block_op_overrides_, std::move(overrides));
99 }
100 
101 void BlockTriangularPreconditioner::LowerSweep(const mfem::Vector& in, mfem::Vector& out) const
102 {
103  mfem::BlockVector b(const_cast<mfem::Vector&>(in), block_offsets_);
104  mfem::BlockVector x(out, block_offsets_);
105 
106  // Forward sweep: i = 0 .. num_blocks_ - 1
107  for (int i = 0; i < num_blocks_; i++) {
108  mfem::Vector& bi = b.GetBlock(i);
109  mfem::Vector& xi = x.GetBlock(i);
110 
111  // rhs_i = b_i
112  mfem::Vector rhs_i(bi.Size());
113  rhs_i = bi;
114 
115  // Subtract sum_{j < i} A_ij x_j
116  for (int j = 0; j < i; j++) {
117  if (block_jacobian_->IsZeroBlock(i, j)) {
118  continue; // no coupling
119  }
120  const mfem::Operator& A_ij = block_jacobian_->GetBlock(i, j);
121 
122  mfem::Vector tmp(rhs_i.Size());
123  const mfem::Vector& xj = x.GetBlock(j);
124 
125  A_ij.Mult(xj, tmp); // tmp = A_ij x_j
126  rhs_i.Add(-1.0, tmp); // rhs_i -= A_ij x_j
127  }
128 
129  // Solve A_ii x_i = rhs_i with the i-th block solver
130  mfem_solvers_[static_cast<size_t>(i)]->Mult(rhs_i, xi);
131  }
132 }
133 
134 void BlockTriangularPreconditioner::UpperSweep(const mfem::Vector& in, mfem::Vector& out) const
135 {
136  mfem::BlockVector b(const_cast<mfem::Vector&>(in), block_offsets_);
137  mfem::BlockVector x(out, block_offsets_);
138 
139  // Backward sweep: i = num_blocks_ - 1 .. 0
140  for (int i = num_blocks_ - 1; i >= 0; i--) {
141  mfem::Vector& bi = b.GetBlock(i);
142  mfem::Vector& xi = x.GetBlock(i);
143 
144  // rhs_i = b_i
145  mfem::Vector rhs_i(bi.Size());
146  rhs_i = bi;
147 
148  // Subtract sum_{j > i} A_ij x_j
149  for (int j = i + 1; j < num_blocks_; j++) {
150  if (block_jacobian_->IsZeroBlock(i, j)) {
151  continue; // no coupling
152  }
153  const mfem::Operator& A_ij = block_jacobian_->GetBlock(i, j);
154 
155  mfem::Vector tmp(rhs_i.Size());
156  const mfem::Vector& xj = x.GetBlock(j);
157 
158  A_ij.Mult(xj, tmp); // tmp = A_ij x_j
159  rhs_i.Add(-1.0, tmp); // rhs_i -= A_ij x_j
160  }
161 
162  // Solve A_ii x_i = rhs_i
163  mfem_solvers_[static_cast<size_t>(i)]->Mult(rhs_i, xi);
164  }
165 }
166 
167 void BlockTriangularPreconditioner::Mult(const mfem::Vector& in, mfem::Vector& out) const
168 {
169  switch (type_) {
171  // x = P_lower^{-1} b
172  LowerSweep(in, out);
173  break;
174 
176  // x = P_upper^{-1} b
177  UpperSweep(in, out);
178  break;
179 
181  // Symmetric: x = P_upper^{-1} D P_lower^{-1} b
182  // 1) tmp = P_lower^{-1} b
183  mfem::Vector tmp(out.Size());
184  LowerSweep(in, tmp);
185 
186  // 2) tmp = D * tmp where D = diag(A_ii)
187  {
188  mfem::BlockVector tmp_block(tmp, block_offsets_);
189 
190  for (int i = 0; i < num_blocks_; i++) {
191  mfem::Vector& tmp_i = tmp_block.GetBlock(i);
192  mfem::Vector tmp_i_scaled(tmp_i.Size());
193 
194  const mfem::Operator& A_ii = block_jacobian_->GetBlock(i, i);
195  A_ii.Mult(tmp_i, tmp_i_scaled); // tmp_i_scaled = A_ii * tmp_i
196 
197  tmp_i = tmp_i_scaled; // write back into block vector
198  }
199  }
200 
201  // 3) out = P_upper^{-1} tmp
202  UpperSweep(tmp, out);
203  break;
204  }
205  }
206 }
207 
208 void BlockTriangularPreconditioner::SetOperator(const mfem::Operator& jacobian)
209 {
210  height = jacobian.Height();
211  width = jacobian.Width();
212  // Cast the supplied jacobian to a block operator object
213  block_jacobian_ = dynamic_cast<const mfem::BlockOperator*>(&jacobian);
214 
215  // Configure all diagonal solves
216  for (int i = 0; i < num_blocks_; i++) {
217  // Attach operator to solver
218  const mfem::Operator* op = nullptr;
219  const size_t si = static_cast<size_t>(i);
220 
221  if (block_op_overrides_[si]) {
222  op = block_op_overrides_[si].get(); // use override
223  } else {
224  op = &block_jacobian_->GetBlock(i, i); // use Jacobian diagonal block
225  }
226 
227  mfem_solvers_[si]->SetOperator(*op);
228  }
229 }
230 
231 BlockTriangularPreconditioner::~BlockTriangularPreconditioner() {}
232 
234  std::vector<std::unique_ptr<mfem::Solver>> solvers,
235  BlockSchurType type, SchurApproxType approxType,
236  std::vector<BlockOverride> overrides)
237  : block_offsets_(offsets),
238  block_jacobian_(nullptr),
239  solver_diag_(block_offsets_),
240  mfem_solvers_(std::move(solvers)),
241  type_(type),
242  approxType_(approxType),
243  block_op_overrides_(static_cast<size_t>(2))
244 {
245  SLIC_ERROR_IF(block_offsets_.Size() - 1 != 2, "This precondition is specifically for 2X2 block systems");
246 
247  applyOverrides(2, block_op_overrides_, std::move(overrides));
248 
249  if (approxType_ == SchurApproxType::Custom && !block_op_overrides_[1]) {
250  throw std::invalid_argument(
251  "SchurApproxType::Custom requires an override operator for block index 1 (custom Schur operator)");
252  }
253 }
254 
255 void BlockSchurPreconditioner::LowerBlock(const mfem::Vector& in, mfem::Vector& out) const
256 {
257  // Interpret in, out as block vectors: in = [b1; b2], out = [x1; x2]
258  mfem::BlockVector b(const_cast<mfem::Vector&>(in), block_offsets_);
259  mfem::BlockVector x(out, block_offsets_);
260 
261  mfem::Vector& b1 = b.GetBlock(0);
262  mfem::Vector& b2 = b.GetBlock(1);
263  mfem::Vector& x1 = x.GetBlock(0);
264  mfem::Vector& x2 = x.GetBlock(1);
265 
266  // 1) Solve A11 x1 = b1
267  mfem_solvers_[0]->Mult(b1, x1);
268 
269  // 2) Build x2 = b2 - A21 x1
270  A_21_->Mult(x1, x2); // x2 = A21 x1
271  x2.Neg(); // x2 = -A21 x1
272  x2 += b2; // x2 = b2 - A21 x1
273 
274  // 3) Reassign x1.
275  x1 = b1;
276 }
277 
278 void BlockSchurPreconditioner::UpperBlock(const mfem::Vector& in, mfem::Vector& out) const
279 {
280  // Interpret in, out as block vectors: in = [b1; b2], out = [x1; x2]
281  mfem::BlockVector b(const_cast<mfem::Vector&>(in), block_offsets_);
282  mfem::BlockVector x(out, block_offsets_);
283 
284  mfem::Vector& b1 = b.GetBlock(0);
285  mfem::Vector& b2 = b.GetBlock(1);
286  mfem::Vector& x1 = x.GetBlock(0);
287  mfem::Vector& x2 = x.GetBlock(1);
288 
289  // 1) Build x1 = A12 b2
290  mfem::Vector rhs1(b1.Size());
291  A_12_->Mult(b2, rhs1); // rhs1 = A12 b2
292 
293  // 2) Solve A11 x1 = rhs1
294  mfem_solvers_[0]->Mult(rhs1, x1);
295 
296  // 3) Build b1 - A11^-1 A12 b2
297  x1.Neg(); // x1 = -x1
298  x1 += b1; // = b1 - A12 x2
299 
300  // 4) Assign x2
301  x2 = b2;
302 }
303 
304 void BlockSchurPreconditioner::Mult(const mfem::Vector& in, mfem::Vector& out) const
305 {
306  switch (type_) {
308  // x = [A11^-1, 0; 0, S^-1] b
309  solver_diag_.Mult(in, out);
310  break;
311  }
312 
313  case BlockSchurType::Lower: {
314  // x = [A11^-1, 0; 0, S^-1][I, 0; -A21 A11^-1, I] b
315  mfem::Vector tmp(out.Size());
316  LowerBlock(in, tmp);
317  solver_diag_.Mult(tmp, out);
318  break;
319  }
320 
321  case BlockSchurType::Upper: {
322  // x = [I, -A11^-1 A12; 0, I][A11^-1, 0; 0, S^-1] b
323  mfem::Vector tmp(out.Size());
324  solver_diag_.Mult(in, tmp);
325  UpperBlock(tmp, out);
326  break;
327  }
328 
329  case BlockSchurType::Full: {
330  // x = [I, -A11^-1 A12; 0, I][A11^-1, 0; 0, S^-1][I, 0; -A21 A11^-1, I] b
331  mfem::Vector tmp(out.Size());
332  mfem::Vector tmp2(out.Size());
333  LowerBlock(in, tmp);
334  solver_diag_.Mult(tmp, tmp2);
335  UpperBlock(tmp2, out);
336  break;
337  }
338  }
339 }
340 
354 mfem::HypreParMatrix* BlockSchurPreconditioner::BuildSchurDiagApprox_(const mfem::HypreParMatrix& A11,
355  const mfem::HypreParMatrix& A12,
356  const mfem::HypreParMatrix& A21,
357  const mfem::HypreParMatrix& A22) const
358 {
359  // Extract diagonal of A11
360  auto* Md = new mfem::HypreParVector(A11.GetComm(), A11.GetGlobalNumRows(), A11.GetRowStarts());
361  A11.GetDiag(*Md);
362 
363  // Scale rows of A12 by diag(A11)^{-1}
364  auto* A12_scaled = new mfem::HypreParMatrix(A12);
365  A12_scaled->InvScaleRows(*Md);
366 
367  delete Md;
368  Md = nullptr;
369 
370  // Compute A21 * (diag(A11)^{-1} * A12)
371  mfem::HypreParMatrix* A21DinvA12 = mfem::ParMult(&A21, A12_scaled);
372  delete A12_scaled;
373  A12_scaled = nullptr;
374 
375  // S_approx = A22 - A21 * diag(A11)^{-1} * A12
376  mfem::HypreParMatrix* S = mfem::Add(1.0, A22, -1.0, *A21DinvA12);
377  delete A21DinvA12;
378  A21DinvA12 = nullptr;
379 
380  return S; // caller owns
381 }
382 
384 double matrixNorm(const mfem::HypreParMatrix& K)
385 {
386  const mfem::HypreParMatrix* H = &K;
387  hypre_ParCSRMatrix* Hhypre = static_cast<hypre_ParCSRMatrix*>(*H);
388  double Hfronorm;
389  hypre_ParCSRMatrixNormFro(Hhypre, &Hfronorm);
390  return Hfronorm;
391 }
392 
393 void BlockSchurPreconditioner::SetOperator(const mfem::Operator& jacobian)
394 {
395  S_approx_view_ = nullptr;
396  height = jacobian.Height();
397  width = jacobian.Width();
398  block_jacobian_ = dynamic_cast<const mfem::BlockOperator*>(&jacobian);
399  MFEM_VERIFY(block_jacobian_, "Jacobian must be a BlockOperator");
400 
401  auto* A11 = dynamic_cast<const mfem::HypreParMatrix*>(&block_jacobian_->GetBlock(0, 0));
402  auto* A12 = dynamic_cast<const mfem::HypreParMatrix*>(&block_jacobian_->GetBlock(0, 1));
403  auto* A21 = dynamic_cast<const mfem::HypreParMatrix*>(&block_jacobian_->GetBlock(1, 0));
404  auto* A22 = dynamic_cast<const mfem::HypreParMatrix*>(&block_jacobian_->GetBlock(1, 1));
405 
406  MFEM_VERIFY(A11 && A12 && A21 && A22,
407  "All blocks must be HypreParMatrix for assembled Schur complement preconditioner.");
408 
409  if (type_ == BlockSchurType::Lower || type_ == BlockSchurType::Full) {
410  A_21_ = A21;
411  }
412  if (type_ == BlockSchurType::Upper || type_ == BlockSchurType::Full) {
413  A_12_ = A12;
414  }
415  // Diagonal preconditioner for block (0,0)
416  const mfem::Operator* op = nullptr;
417  if (block_op_overrides_[0]) {
418  op = block_op_overrides_[0].get(); // use override
419  } else {
420  op = A11; // use Jacobian diagonal block
421  }
422  mfem_solvers_[0]->SetOperator(*op);
423  // Build Schur complement approximation
424  if (approxType_ == SchurApproxType::DiagInv) {
425  S_approx_owned_.reset(BuildSchurDiagApprox_(*A11, *A12, *A21, *A22));
426  S_approx_view_ = S_approx_owned_.get();
427  } else if (approxType_ == SchurApproxType::A22Only) {
428  S_approx_owned_.reset(new mfem::HypreParMatrix(*A22));
429  S_approx_view_ = S_approx_owned_.get();
430  } else {
431  S_approx_owned_.reset();
432  S_approx_view_ = block_op_overrides_[1].get();
433  }
434 
435  MFEM_VERIFY(S_approx_view_, "Schur complement approximation operator must be set");
436 
437  // Set the Schur complement preconditioner for block (1,1)
438  mfem_solvers_[1]->SetOperator(*S_approx_view_);
439 
440  // Set up block diagonal operator
441  solver_diag_.SetBlock(0, 0, mfem_solvers_[0].get());
442  solver_diag_.SetBlock(1, 1, mfem_solvers_[1].get());
443 }
444 
445 BlockSchurPreconditioner::~BlockSchurPreconditioner() {}
446 } // namespace smith
BlockDiagonalPreconditioner(mfem::Array< int > &offsets, std::vector< std::unique_ptr< mfem::Solver >> solvers, std::vector< BlockOverride > overrides={})
Construct a new N by N block diagonal preconditioner.
virtual void Mult(const mfem::Vector &in, mfem::Vector &out) const
The action of the precondition on the block vector (b_1, ..., b_n)
virtual void SetOperator(const mfem::Operator &jacobian)
Set the preconditioner to use the supplied linearized block Jacobian.
virtual void Mult(const mfem::Vector &in, mfem::Vector &out) const
The action of the precondition on the block vector (b_1, b_2)
BlockSchurPreconditioner(mfem::Array< int > &offsets, std::vector< std::unique_ptr< mfem::Solver >> solvers, BlockSchurType type=BlockSchurType::Diagonal, SchurApproxType approxType=SchurApproxType::DiagInv, std::vector< BlockOverride > overrides={})
Construct a new 2x2 block Schur complement preconditioner.
virtual void SetOperator(const mfem::Operator &jacobian)
Set the preconditioner to use the supplied linearized block Jacobian.
BlockTriangularPreconditioner(mfem::Array< int > &offsets, std::vector< std::unique_ptr< mfem::Solver >> solvers, BlockTriangularType type=BlockTriangularType::Lower, std::vector< BlockOverride > overrides={})
Construct a new nxn block triangular preconditioner.
virtual void Mult(const mfem::Vector &in, mfem::Vector &out) const
The action of the precondition on the block vector (b_1, ..., b_n)
virtual void SetOperator(const mfem::Operator &jacobian)
Set the preconditioner to use the supplied linearized block Jacobian.
Accelerator functionality.
Definition: smith.cpp:36
constexpr T & get(variant< T0, T1 > &v)
Returns the variant member of specified type.
Definition: variant.hpp:338
SchurApproxType
Selects how the (1,1) Schur operator is approximated.
double matrixNorm(std::unique_ptr< mfem::HypreParMatrix > &K)
Utility to compute the matrix norm.
constexpr SMITH_HOST_DEVICE auto type(const tuple< T... > &values)
a function intended to be used for extracting the ith type from a tuple.
Definition: tuple.hpp:376
BlockTriangularType
Selects the block triangular sweep used by BlockTriangularPreconditioner.
BlockSchurType
Selects the block Schur preconditioner variant.