Nektar++
GlobalLinSysIterative.cpp
Go to the documentation of this file.
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 // File: GlobalLinSysIterative.cpp
4 //
5 // For more information, please see: http://www.nektar.info
6 //
7 // The MIT License
8 //
9 // Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
10 // Department of Aeronautics, Imperial College London (UK), and Scientific
11 // Computing and Imaging Institute, University of Utah (USA).
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining a
14 // copy of this software and associated documentation files (the "Software"),
15 // to deal in the Software without restriction, including without limitation
16 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 // and/or sell copies of the Software, and to permit persons to whom the
18 // Software is furnished to do so, subject to the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be included
21 // in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 // DEALINGS IN THE SOFTWARE.
30 //
31 // Description: GlobalLinSysIterative definition
32 //
33 ///////////////////////////////////////////////////////////////////////////////
34 
36 
37 using namespace std;
38 
39 namespace Nektar
40 {
41  namespace MultiRegions
42  {
43  /**
44  * @class GlobalLinSysIterative
45  *
46  * Solves a linear system using iterative methods.
47  */
48 
49  /// Constructor for full direct matrix solve.
50  GlobalLinSysIterative::GlobalLinSysIterative(
51  const GlobalLinSysKey &pKey,
52  const std::weak_ptr<ExpList> &pExpList,
53  const std::shared_ptr<AssemblyMap>
54  &pLocToGloMap)
55  : GlobalLinSys(pKey, pExpList, pLocToGloMap),
56  m_rhs_magnitude(NekConstants::kNekUnsetDouble),
57  m_rhs_mag_sm(0.9),
59  m_totalIterations(0),
60  m_useProjection(false),
61  m_numPrevSols(0)
62  {
63  m_tolerance = pLocToGloMap->GetIterativeTolerance();
64  m_maxiter = pLocToGloMap->GetMaxIterations();
65 
66  LibUtilities::CommSharedPtr vComm = m_expList.lock()->GetComm()->GetRowComm();
67  m_root = (vComm->GetRank())? false : true;
68 
69  int successiveRHS;
70 
71  if((successiveRHS = pLocToGloMap->GetSuccessiveRHS()))
72  {
73  m_prevLinSol.set_capacity(successiveRHS);
74  m_useProjection = true;
75  }
76  else
77  {
78  m_useProjection = false;
79  }
80  }
81 
83  {
84  }
85 
86 
87  /**
88  *
89  */
91  const int nGlobal,
92  const Array<OneD,const NekDouble> &pInput,
93  Array<OneD, NekDouble> &pOutput,
94  const AssemblyMapSharedPtr &plocToGloMap,
95  const int nDir)
96  {
97  if (m_useProjection)
98  {
99  DoAconjugateProjection(nGlobal, pInput, pOutput, plocToGloMap, nDir);
100  }
101  else
102  {
103  // applying plain Conjugate Gradient
104  DoConjugateGradient(nGlobal, pInput, pOutput, plocToGloMap, nDir);
105  }
106  }
107 
108 
109  /**
110  * This method implements A-conjugate projection technique
111  * in order to speed up successive linear solves with
112  * right-hand sides arising from time-dependent discretisations.
113  * (P.F.Fischer, Comput. Methods Appl. Mech. Engrg. 163, 1998)
114  */
116  const int nGlobal,
117  const Array<OneD,const NekDouble> &pInput,
118  Array<OneD, NekDouble> &pOutput,
119  const AssemblyMapSharedPtr &plocToGloMap,
120  const int nDir)
121  {
122  // Get the communicator for performing data exchanges
124  = m_expList.lock()->GetComm()->GetRowComm();
125 
126  // Get vector sizes
127  int nNonDir = nGlobal - nDir;
129 
130  if (0 == m_numPrevSols)
131  {
132  // no previous solutions found, call CG
133 
134  DoConjugateGradient(nGlobal, pInput, pOutput, plocToGloMap, nDir);
135 
136  UpdateKnownSolutions(nGlobal, pOutput, nDir);
137  }
138  else
139  {
140  // Create NekVector wrappers for linear algebra operations
141  NekVector<NekDouble> b (nNonDir, pInput + nDir, eWrapper);
142  NekVector<NekDouble> x (nNonDir, tmp = pOutput + nDir, eWrapper);
143 
144  // check the input vector (rhs) is not zero
145 
146  NekDouble rhsNorm = Vmath::Dot2(nNonDir,
147  pInput + nDir,
148  pInput + nDir,
149  m_map + nDir);
150 
151  vComm->AllReduce(rhsNorm, Nektar::LibUtilities::ReduceSum);
152 
153  if (rhsNorm < NekConstants::kNekZeroTol)
154  {
155  Array<OneD, NekDouble> tmp = pOutput+nDir;
156  Vmath::Zero(nNonDir, tmp, 1);
157  return;
158  }
159 
160  // Allocate array storage
161  Array<OneD, NekDouble> px_s (nGlobal, 0.0);
162  Array<OneD, NekDouble> pb_s (nGlobal, 0.0);
163  Array<OneD, NekDouble> tmpAx_s (nGlobal, 0.0);
164  Array<OneD, NekDouble> tmpx_s (nGlobal, 0.0);
165 
166  NekVector<NekDouble> pb (nNonDir, tmp = pb_s + nDir, eWrapper);
167  NekVector<NekDouble> px (nNonDir, tmp = px_s + nDir, eWrapper);
168  NekVector<NekDouble> tmpAx (nNonDir, tmp = tmpAx_s + nDir, eWrapper);
169  NekVector<NekDouble> tmpx (nNonDir, tmp = tmpx_s + nDir, eWrapper);
170 
171 
172  // notation follows the paper cited:
173  // \alpha_i = \tilda{x_i}^T b^n
174  // projected x, px = \sum \alpha_i \tilda{x_i}
175 
176  Array<OneD, NekDouble> alpha (m_prevLinSol.size(), 0.0);
177  for (int i = 0; i < m_prevLinSol.size(); i++)
178  {
179  alpha[i] = Vmath::Dot2(nNonDir,
180  m_prevLinSol[i],
181  pInput + nDir,
182  m_map + nDir);
183  }
184  vComm->AllReduce(alpha, Nektar::LibUtilities::ReduceSum);
185 
186  for (int i = 0; i < m_prevLinSol.size(); i++)
187  {
188  if (alpha[i] < NekConstants::kNekZeroTol)
189  {
190  continue;
191  }
192 
193  NekVector<NekDouble> xi (nNonDir, m_prevLinSol[i], eWrapper);
194  px += alpha[i] * xi;
195  }
196 
197  // pb = b^n - A px
198  Vmath::Vcopy(nNonDir,
199  pInput.get() + nDir, 1,
200  pb_s.get() + nDir, 1);
201 
202  v_DoMatrixMultiply(px_s, tmpAx_s);
203 
204  pb -= tmpAx;
205 
206 
207  // solve the system with projected rhs
208  DoConjugateGradient(nGlobal, pb_s, tmpx_s, plocToGloMap, nDir);
209 
210 
211  // remainder solution + projection of previous solutions
212  x = tmpx + px;
213 
214  // save the auxiliary solution to prev. known solutions
215  UpdateKnownSolutions(nGlobal, tmpx_s, nDir);
216  }
217  }
218 
219 
220  /**
221  * Calculating A-norm of an input vector,
222  * A-norm(x) := sqrt( < x, Ax > )
223  */
225  const int nGlobal,
226  const Array<OneD,const NekDouble> &in,
227  const int nDir)
228  {
229  // Get the communicator for performing data exchanges
231  = m_expList.lock()->GetComm()->GetRowComm();
232 
233  // Get vector sizes
234  int nNonDir = nGlobal - nDir;
235 
236  // Allocate array storage
237  Array<OneD, NekDouble> tmpAx_s (nGlobal, 0.0);
238 
239  v_DoMatrixMultiply(in, tmpAx_s);
240 
241  NekDouble anorm_sq = Vmath::Dot2(nNonDir,
242  in + nDir,
243  tmpAx_s + nDir,
244  m_map + nDir);
245  vComm->AllReduce(anorm_sq, Nektar::LibUtilities::ReduceSum);
246  return std::sqrt(anorm_sq);
247  }
248 
249  /**
250  * Updates the storage of previously known solutions.
251  * Performs normalisation of input vector wrt A-norm.
252  */
254  const int nGlobal,
255  const Array<OneD,const NekDouble> &newX,
256  const int nDir)
257  {
258  // Get vector sizes
259  int nNonDir = nGlobal - nDir;
260 
261  // Get the communicator for performing data exchanges
263  = m_expList.lock()->GetComm()->GetRowComm();
264 
265  // Check the solution is non-zero
266  NekDouble solNorm = Vmath::Dot2(nNonDir,
267  newX + nDir,
268  newX + nDir,
269  m_map + nDir);
270  vComm->AllReduce(solNorm, Nektar::LibUtilities::ReduceSum);
271 
272  if (solNorm < NekConstants::kNekZeroTol)
273  {
274  return;
275  }
276 
277 
278  // Allocate array storage
279  Array<OneD, NekDouble> tmpAx_s (nGlobal, 0.0);
280  Array<OneD, NekDouble> px_s (nGlobal, 0.0);
281  Array<OneD, NekDouble> tmp1, tmp2;
282 
283  // Create NekVector wrappers for linear algebra operations
284  NekVector<NekDouble> px (nNonDir, tmp1 = px_s + nDir, eWrapper);
285  NekVector<NekDouble> tmpAx (nNonDir, tmp2 = tmpAx_s + nDir, eWrapper);
286 
287 
288  // calculating \tilda{x} - sum \alpha_i\tilda{x}_i
289 
290  Vmath::Vcopy(nNonDir,
291  tmp1 = newX + nDir, 1,
292  tmp2 = px_s + nDir, 1);
293 
294  if (m_prevLinSol.size() > 0)
295  {
296  v_DoMatrixMultiply(newX, tmpAx_s);
297  }
298 
299  Array<OneD, NekDouble> alpha (m_prevLinSol.size(), 0.0);
300  for (int i = 0; i < m_prevLinSol.size(); i++)
301  {
302  alpha[i] = Vmath::Dot2(nNonDir,
303  m_prevLinSol[i],
304  tmpAx_s + nDir,
305  m_map + nDir);
306  }
307  vComm->AllReduce(alpha, Nektar::LibUtilities::ReduceSum);
308 
309  for (int i = 0; i < m_prevLinSol.size(); i++)
310  {
311  if (alpha[i] < NekConstants::kNekZeroTol)
312  {
313  continue;
314  }
315 
316  NekVector<NekDouble> xi (nNonDir, m_prevLinSol[i], eWrapper);
317  px -= alpha[i] * xi;
318  }
319 
320 
321  // Some solutions generated by CG are identical zeros, see
322  // solutions generated for Test_Tet_equitri.xml (IncNavierStokesSolver).
323  // Not going to store identically zero solutions.
324 
325  NekDouble anorm = CalculateAnorm(nGlobal, px_s, nDir);
326  if (anorm < NekConstants::kNekZeroTol)
327  {
328  return;
329  }
330 
331  // normalisation of new solution
332  Vmath::Smul(nNonDir, 1.0/anorm, px_s.get() + nDir, 1, px_s.get() + nDir, 1);
333 
334  // updating storage with non-Dirichlet-dof part of new solution vector
335  m_prevLinSol.push_back(px_s + nDir);
336  m_numPrevSols++;
337  }
338 
339 
340 
341  /**  
342  * Solve a global linear system using the conjugate gradient method.  
343  * We solve only for the non-Dirichlet modes. The operator is evaluated  
344  * using an auxiliary function v_DoMatrixMultiply defined by the  
345  * specific solver. Distributed math routines are used to support  
346  * parallel execution of the solver.  
347  *  
348  * The implemented algorithm uses a reduced-communication reordering of  
349  * the standard PCG method (Demmel, Heath and Vorst, 1993)  
350  *  
351  * @param pInput Input residual of all DOFs.  
352  * @param pOutput Solution vector of all DOFs.  
353  */
355  const int nGlobal,
356  const Array<OneD,const NekDouble> &pInput,
357  Array<OneD, NekDouble> &pOutput,
358  const AssemblyMapSharedPtr &plocToGloMap,
359  const int nDir)
360  {
361  if (!m_precon)
362  {
363  v_UniqueMap();
364  m_precon = CreatePrecon(plocToGloMap);
365  m_precon->BuildPreconditioner();
366  }
367 
368  // Get the communicator for performing data exchanges
370  = m_expList.lock()->GetComm()->GetRowComm();
371 
372  // Get vector sizes
373  int nNonDir = nGlobal - nDir;
374 
375  // Allocate array storage
376  Array<OneD, NekDouble> w_A (nGlobal, 0.0);
377  Array<OneD, NekDouble> s_A (nGlobal, 0.0);
378  Array<OneD, NekDouble> p_A (nNonDir, 0.0);
379  Array<OneD, NekDouble> r_A (nNonDir, 0.0);
380  Array<OneD, NekDouble> q_A (nNonDir, 0.0);
382 
383  // Create NekVector wrappers for linear algebra operations
384  NekVector<NekDouble> in (nNonDir,pInput + nDir, eWrapper);
385  NekVector<NekDouble> out(nNonDir,tmp = pOutput + nDir,eWrapper);
386  NekVector<NekDouble> w (nNonDir,tmp = w_A + nDir, eWrapper);
387  NekVector<NekDouble> s (nNonDir,tmp = s_A + nDir, eWrapper);
388  NekVector<NekDouble> p (nNonDir,p_A, eWrapper);
389  NekVector<NekDouble> r (nNonDir,r_A, eWrapper);
390  NekVector<NekDouble> q (nNonDir,q_A, eWrapper);
391 
392  int k;
393  NekDouble alpha, beta, rho, rho_new, mu, eps, min_resid;
394  Array<OneD, NekDouble> vExchange(3,0.0);
395 
396  // Copy initial residual from input
397  r = in;
398  // zero homogeneous out array ready for solution updates
399  // Should not be earlier in case input vector is same as
400  // output and above copy has been peformed
401  Vmath::Zero(nNonDir,tmp = pOutput + nDir,1);
402 
403 
404  // evaluate initial residual error for exit check
405  vExchange[2] = Vmath::Dot2(nNonDir,
406  r_A,
407  r_A,
408  m_map + nDir);
409 
410  vComm->AllReduce(vExchange, Nektar::LibUtilities::ReduceSum);
411 
412  eps = vExchange[2];
413 
415  {
416  NekVector<NekDouble> inGlob (nGlobal, pInput, eWrapper);
417  Set_Rhs_Magnitude(inGlob);
418  }
419 
420  m_totalIterations = 0;
421 
422  // If input residual is less than tolerance skip solve.
424  {
425  if (m_verbose && m_root)
426  {
427  cout << "CG iterations made = " << m_totalIterations
428  << " using tolerance of " << m_tolerance
429  << " (error = " << sqrt(eps/m_rhs_magnitude)
430  << ", rhs_mag = " << sqrt(m_rhs_magnitude) << ")"
431  << endl;
432  }
433  return;
434  }
435 
436  m_precon->DoPreconditioner(r_A, tmp = w_A + nDir);
437 
438  v_DoMatrixMultiply(w_A, s_A);
439 
440  k = 0;
441 
442  vExchange[0] = Vmath::Dot2(nNonDir,
443  r_A,
444  w_A + nDir,
445  m_map + nDir);
446 
447  vExchange[1] = Vmath::Dot2(nNonDir,
448  s_A + nDir,
449  w_A + nDir,
450  m_map + nDir);
451 
452  vComm->AllReduce(vExchange, Nektar::LibUtilities::ReduceSum);
453 
454  rho = vExchange[0];
455  mu = vExchange[1];
456  min_resid = m_rhs_magnitude;
457  beta = 0.0;
458  alpha = rho/mu;
459  m_totalIterations = 1;
460 
461  // Continue until convergence
462  while (true)
463  {
464  if(k >= m_maxiter)
465  {
466  if (m_root)
467  {
468  cout << "CG iterations made = " << m_totalIterations
469  << " using tolerance of " << m_tolerance
470  << " (error = " << sqrt(eps/m_rhs_magnitude)
471  << ", rhs_mag = " << sqrt(m_rhs_magnitude) << ")"
472  << endl;
473  }
475  "Exceeded maximum number of iterations");
476  }
477 
478  // Compute new search direction p_k, q_k
479  Vmath::Svtvp(nNonDir, beta, &p_A[0], 1, &w_A[nDir], 1, &p_A[0], 1);
480  Vmath::Svtvp(nNonDir, beta, &q_A[0], 1, &s_A[nDir], 1, &q_A[0], 1);
481 
482  // Update solution x_{k+1}
483  Vmath::Svtvp(nNonDir, alpha, &p_A[0], 1, &pOutput[nDir], 1, &pOutput[nDir], 1);
484 
485  // Update residual vector r_{k+1}
486  Vmath::Svtvp(nNonDir, -alpha, &q_A[0], 1, &r_A[0], 1, &r_A[0], 1);
487 
488  // Apply preconditioner
489  m_precon->DoPreconditioner(r_A, tmp = w_A + nDir);
490 
491  // Perform the method-specific matrix-vector multiply operation.
492  v_DoMatrixMultiply(w_A, s_A);
493 
494  // <r_{k+1}, w_{k+1}>
495  vExchange[0] = Vmath::Dot2(nNonDir,
496  r_A,
497  w_A + nDir,
498  m_map + nDir);
499  // <s_{k+1}, w_{k+1}>
500  vExchange[1] = Vmath::Dot2(nNonDir,
501  s_A + nDir,
502  w_A + nDir,
503  m_map + nDir);
504 
505  // <r_{k+1}, r_{k+1}>
506  vExchange[2] = Vmath::Dot2(nNonDir,
507  r_A,
508  r_A,
509  m_map + nDir);
510 
511  // Perform inner-product exchanges
512  vComm->AllReduce(vExchange, Nektar::LibUtilities::ReduceSum);
513 
514  rho_new = vExchange[0];
515  mu = vExchange[1];
516  eps = vExchange[2];
517 
519 
520  // test if norm is within tolerance
521  if (eps < m_tolerance * m_tolerance * m_rhs_magnitude)
522  {
523  if (m_verbose && m_root)
524  {
525  cout << "CG iterations made = " << m_totalIterations
526  << " using tolerance of " << m_tolerance
527  << " (error = " << sqrt(eps/m_rhs_magnitude)
528  << ", rhs_mag = " << sqrt(m_rhs_magnitude) << ")"
529  << endl;
530  }
531  break;
532  }
533  min_resid = min(min_resid, eps);
534 
535  // Compute search direction and solution coefficients
536  beta = rho_new/rho;
537  alpha = rho_new/(mu - rho_new*beta/alpha);
538  rho = rho_new;
539  k++;
540  }
541  }
542 
544  const NekVector<NekDouble> &pIn)
545  {
546  Array<OneD, NekDouble> vExchange(1, 0.0);
547  if (m_map.num_elements() > 0)
548  {
549  vExchange[0] = Vmath::Dot2(pIn.GetDimension(),
550  &pIn[0],&pIn[0],&m_map[0]);
551  }
552 
553  m_expList.lock()->GetComm()->GetRowComm()->AllReduce(
555 
556  // To ensure that very different rhs values are not being
557  // used in subsequent solvers such as the velocit solve in
558  // INC NS. If this works we then need to work out a better
559  // way to control this.
560  NekDouble new_rhs_mag = (vExchange[0] > 1e-6)? vExchange[0] : 1.0;
561 
563  {
564  m_rhs_magnitude = new_rhs_mag;
565  }
566  else
567  {
569  (1.0-m_rhs_mag_sm)*new_rhs_mag);
570  }
571  }
572 
573  }
574 }
NekDouble m_rhs_mag_sm
cnt to how many times rhs_magnitude is called
void Set_Rhs_Magnitude(const NekVector< NekDouble > &pIn)
void UpdateKnownSolutions(const int pGlobalBndDofs, const Array< OneD, const NekDouble > &pSolution, const int pNumDirBndDofs)
std::shared_ptr< Comm > CommSharedPtr
Pointer to a Communicator object.
Definition: Comm.h:53
void Svtvp(int n, const T alpha, const T *x, const int incx, const T *y, const int incy, T *z, const int incz)
svtvp (scalar times vector plus vector): z = alpha*x + y
Definition: Vmath.cpp:488
bool m_useProjection
Whether to apply projection technique.
STL namespace.
boost::circular_buffer< Array< OneD, NekDouble > > m_prevLinSol
Storage for solutions to previous linear problems.
virtual void v_DoMatrixMultiply(const Array< OneD, NekDouble > &pInput, Array< OneD, NekDouble > &pOutput)=0
std::shared_ptr< AssemblyMap > AssemblyMapSharedPtr
Definition: AssemblyMap.h:52
NekDouble CalculateAnorm(const int nGlobal, const Array< OneD, const NekDouble > &in, const int nDir)
static const NekDouble kNekZeroTol
void Smul(int n, const T alpha, const T *x, const int incx, T *y, const int incy)
Scalar multiply y = alpha*y.
Definition: Vmath.cpp:216
static PreconditionerSharedPtr NullPreconditionerSharedPtr
NekDouble m_tolerance
Tolerance of iterative solver.
int m_numPrevSols
Total counter of previous solutions.
Array< OneD, int > m_map
Global to universal unique map.
double NekDouble
static const NekDouble kNekUnsetDouble
Describe a linear system.
void DoAconjugateProjection(const int pNumRows, const Array< OneD, const NekDouble > &pInput, Array< OneD, NekDouble > &pOutput, const AssemblyMapSharedPtr &locToGloMap, const int pNumDir)
A-conjugate projection technique.
NekDouble m_rhs_magnitude
dot product of rhs to normalise stopping criterion
A global linear system.
Definition: GlobalLinSys.h:72
const std::weak_ptr< ExpList > m_expList
Local Matrix System.
Definition: GlobalLinSys.h:127
T Dot2(int n, const T *w, const T *x, const int *y)
vvtvp (vector times vector times vector): z = w*x*y
Definition: Vmath.cpp:957
#define ROOTONLY_NEKERROR(type, msg)
Definition: ErrorUtil.hpp:213
unsigned int GetDimension() const
Returns the number of dimensions for the point.
Definition: NekVector.cpp:209
virtual void v_SolveLinearSystem(const int pNumRows, const Array< OneD, const NekDouble > &pInput, Array< OneD, NekDouble > &pOutput, const AssemblyMapSharedPtr &locToGloMap, const int pNumDir)
Solve the matrix system.
void Zero(int n, T *x, const int incx)
Zero vector.
Definition: Vmath.cpp:376
void Vcopy(int n, const T *x, const int incx, T *y, const int incy)
Definition: Vmath.cpp:1064
void DoConjugateGradient(const int pNumRows, const Array< OneD, const NekDouble > &pInput, Array< OneD, NekDouble > &pOutput, const AssemblyMapSharedPtr &locToGloMap, const int pNumDir)
Actual iterative solve.
PreconditionerSharedPtr CreatePrecon(AssemblyMapSharedPtr asmMap)
Create a preconditioner object from the parameters defined in the supplied assembly map...