Nektar++
NekLinSysIterGMRES.cpp
Go to the documentation of this file.
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 // File: NekLinSysIterGMRES.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 // License for the specific language governing rights and limitations under
14 // Permission is hereby granted, free of charge, to any person obtaining a
15 // copy of this software and associated documentation files (the "Software"),
16 // to deal in the Software without restriction, including without limitation
17 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
18 // and/or sell copies of the Software, and to permit persons to whom the
19 // Software is furnished to do so, subject to the following conditions:
20 //
21 // The above copyright notice and this permission notice shall be included
22 // in all copies or substantial portions of the Software.
23 //
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
25 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
27 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30 // DEALINGS IN THE SOFTWARE.
31 //
32 // Description: NekLinSysIterGMRES definition
33 //
34 ///////////////////////////////////////////////////////////////////////////////
35 
38 
39 using namespace std;
40 
41 namespace Nektar
42 {
43 namespace LibUtilities
44 {
45 /**
46  * @class NekLinSysIterGMRES
47  *
48  * Solves a linear system using iterative methods.
49  */
50 string NekLinSysIterGMRES::className =
52  "GMRES", NekLinSysIterGMRES::create, "NekLinSysIterGMRES solver.");
53 
54 NekLinSysIterGMRES::NekLinSysIterGMRES(
56  const LibUtilities::CommSharedPtr &vComm, const int nDimen,
57  const NekSysKey &pKey)
58  : NekLinSysIter(pSession, vComm, nDimen, pKey)
59 {
60  std::vector<std::string> variables(1);
61  variables[0] = pSession->GetVariable(0);
62  string variable = variables[0];
63 
64  pSession->MatchSolverInfo("GMRESLeftPrecon", "True", m_NekLinSysLeftPrecon,
66  pSession->MatchSolverInfo("GMRESRightPrecon", "False",
69 
70  if (pSession->DefinesGlobalSysSolnInfo(variable, "GMRESMaxHessMatBand"))
71  {
72  m_KrylovMaxHessMatBand = boost::lexical_cast<int>(
73  pSession->GetGlobalSysSolnInfo(variable, "GMRESMaxHessMatBand")
74  .c_str());
75  }
76  else
77  {
78  pSession->LoadParameter("GMRESMaxHessMatBand", m_KrylovMaxHessMatBand,
79  m_LinSysMaxStorage + 1);
80  }
81 
84 
85  int GMRESCentralDifference = 0;
86  pSession->LoadParameter("GMRESCentralDifference", GMRESCentralDifference,
87  0);
88 
89  switch (GMRESCentralDifference)
90  {
91  case 1:
92  m_DifferenceFlag0 = true;
93  m_DifferenceFlag1 = false;
94  break;
95  case 2:
96  m_DifferenceFlag0 = true;
97  m_DifferenceFlag1 = true;
98  break;
99 
100  default:
101  m_DifferenceFlag0 = false;
102  m_DifferenceFlag1 = false;
103  break;
104  }
105 }
106 
108 {
110 }
111 
113 {
114 }
115 
116 /**
117  *
118  */
120  const int nGlobal, const Array<OneD, const NekDouble> &pInput,
121  Array<OneD, NekDouble> &pOutput, const int nDir, const NekDouble tol,
122  const NekDouble factor)
123 {
124  boost::ignore_unused(tol);
125 
126  m_tolerance = max(tol, 1.0E-16);
127  m_prec_factor = factor;
128  int niterations = DoGMRES(nGlobal, pInput, pOutput, nDir);
129 
130  return niterations;
131 }
132 
133 /**  
134  * Solve a global linear system using the Gmres 
135  * We solve only for the non-Dirichlet modes. The operator is evaluated  
136  * using an auxiliary function v_DoMatrixMultiply defined by the  
137  * specific solver. Distributed math routines are used to support  
138  * parallel execution of the solver.  
139  *  
140  * @param pInput Input residual of all DOFs.  
141  * @param pOutput Solution vector of all DOFs.  
142  */
143 
144 int NekLinSysIterGMRES::DoGMRES(const int nGlobal,
145  const Array<OneD, const NekDouble> &pInput,
146  Array<OneD, NekDouble> &pOutput, const int nDir)
147 {
149 
151  {
152  NekVector<NekDouble> inGlob(nGlobal, pInput, eWrapper);
153  Set_Rhs_Magnitude(inGlob);
154  }
155 
156  m_rhs_magnitude = 1.0;
157 
158  // Get vector sizes
159  NekDouble eps = 0.0;
160  int nNonDir = nGlobal - nDir;
161 
163 
164  // zero homogeneous out array ready for solution updates
165  // Should not be earlier in case input vector is same as
166  // output and above copy has been peformed
167  Vmath::Zero(nNonDir, tmp = pOutput + nDir, 1);
168 
169  m_totalIterations = 0;
170  m_converged = false;
171 
172  bool restarted = false;
173  bool truncted = false;
174 
175  if (m_KrylovMaxHessMatBand > 0)
176  {
177  truncted = true;
178  }
179 
180  int nwidthcolm = 13;
181 
182  for (int nrestart = 0; nrestart < m_maxrestart; ++nrestart)
183  {
184  eps =
185  DoGmresRestart(restarted, truncted, nGlobal, pInput, pOutput, nDir);
186 
187  if (m_converged)
188  {
189  break;
190  }
191  restarted = true;
192  }
193 
194  if (m_verbose)
195  {
196  Array<OneD, NekDouble> r0(nGlobal, 0.0);
198  Vmath::Svtvp(nNonDir, -1.0, &r0[0] + nDir, 1, &pInput[0] + nDir, 1,
199  &r0[0] + nDir, 1);
200  NekDouble vExchange = Vmath::Dot2(nNonDir, &r0[0] + nDir, &r0[0] + nDir,
201  &m_map[0] + nDir);
202  m_Comm->AllReduce(vExchange, LibUtilities::ReduceSum);
203  NekDouble eps1 = vExchange;
204 
205  if (m_root)
206  {
207  cout << std::scientific << std::setw(nwidthcolm)
208  << std::setprecision(nwidthcolm - 8)
209  << " GMRES iterations made = " << m_totalIterations
210  << " using tolerance of " << m_tolerance
211  << " (error = " << sqrt(eps * m_prec_factor / m_rhs_magnitude)
212  << ")";
213 
214  cout << " WITH (GMRES eps = " << eps << " REAL eps= " << eps1
215  << ")";
216 
217  if (m_converged)
218  {
219  cout << " CONVERGED" << endl;
220  }
221  else
222  {
223  cout << " WARNING: Exceeded maxIt" << endl;
224  }
225  }
226  }
227 
228  if (m_FlagWarnings)
229  {
230  WARNINGL1(m_converged, "GMRES did not converge.");
231  }
232  return m_totalIterations;
233 }
234 
236  const bool restarted, const bool truncted, const int nGlobal,
238  const int nDir)
239 {
240  int nNonDir = nGlobal - nDir;
241 
242  // Allocate array storage of coefficients
243  // Hessenburg matrix
245  for (int i = 0; i < m_LinSysMaxStorage; ++i)
246  {
247  hes[i] = Array<OneD, NekDouble>(m_LinSysMaxStorage + 1, 0.0);
248  }
249  // Hesseburg matrix after rotation
251  for (int i = 0; i < m_LinSysMaxStorage; ++i)
252  {
253  Upper[i] = Array<OneD, NekDouble>(m_LinSysMaxStorage + 1, 0.0);
254  }
255  // Total search directions
257  for (int i = 0; i < m_LinSysMaxStorage + 1; ++i)
258  {
259  V_total[i] = Array<OneD, NekDouble>(nGlobal, 0.0);
260  }
261  // Residual
263  // Givens rotation c
265  // Givens rotation s
267  // Total coefficients, just for check
269  // Residual
270  NekDouble eps;
271  // Search direction order
275  // Temporary variables
276  int idtem;
277  int starttem;
278  int endtem;
279 
280  NekDouble beta, alpha;
281  NekDouble vExchange = 0;
282  // Temporary Array
283  Array<OneD, NekDouble> r0(nGlobal, 0.0);
286  Array<OneD, NekDouble> Vsingle1;
287  Array<OneD, NekDouble> Vsingle2;
288  Array<OneD, NekDouble> hsingle1;
289  Array<OneD, NekDouble> hsingle2;
290 
291  if (restarted)
292  {
293  // This is tmp2=Ax
295 
296  // The first search direction
297  beta = -1.0;
298  // This is r0=b-AX
299  Vmath::Svtvp(nNonDir, beta, &r0[0] + nDir, 1, &pInput[0] + nDir, 1,
300  &r0[0] + nDir, 1);
301  }
302  else
303  {
304  // If not restarted, x0 should be zero
305  Vmath::Vcopy(nNonDir, &pInput[0] + nDir, 1, &r0[0] + nDir, 1);
306  }
307 
309  {
310  tmp1 = r0 + nDir;
311  tmp2 = r0 + nDir;
312  m_operator.DoNekSysPrecon(tmp1, tmp2);
313  }
314 
315  // Norm of (r0)
316  // The m_map tells how to connect
317  vExchange =
318  Vmath::Dot2(nNonDir, &r0[0] + nDir, &r0[0] + nDir, &m_map[0] + nDir);
319  m_Comm->AllReduce(vExchange, LibUtilities::ReduceSum);
320  eps = vExchange;
321 
322  if (!restarted)
323  {
325  {
327  {
328  // Evaluate initial residual error for exit check
329  vExchange = Vmath::Dot2(nNonDir, &pInput[0] + nDir,
330  &pInput[0] + nDir, &m_map[0] + nDir);
331  m_Comm->AllReduce(vExchange, LibUtilities::ReduceSum);
332  m_prec_factor = vExchange / eps;
333  }
334  else
335  {
336  m_prec_factor = 1.0;
337  }
338  }
339  }
340 
341  tmp2 = r0 + nDir;
342  Vmath::Smul(nNonDir, sqrt(m_prec_factor), tmp2, 1, tmp2, 1);
343  eps = eps * m_prec_factor;
344  eta[0] = sqrt(eps);
345 
346  // Give an order for the entries in Hessenburg matrix
347  for (int nd = 0; nd < m_LinSysMaxStorage; ++nd)
348  {
349  id[nd] = nd;
350  id_end[nd] = nd + 1;
351  starttem = id_end[nd] - m_KrylovMaxHessMatBand;
352  if (truncted && (starttem) > 0)
353  {
354  id_start[nd] = starttem;
355  }
356  else
357  {
358  id_start[nd] = 0;
359  }
360  }
361 
362  // Normlized by r0 norm V(:,1)=r0/norm(r0)
363  alpha = 1.0 / eta[0];
364  // Scalar multiplication
365  Vmath::Smul(nNonDir, alpha, &r0[0] + nDir, 1, &V_total[0][0] + nDir, 1);
366 
367  // restarted Gmres(m) process
368  int nswp = 0;
370  {
371  Vsingle1 = Array<OneD, NekDouble>(nGlobal, 0.0);
372  }
373 
374  for (int nd = 0; nd < m_LinSysMaxStorage; ++nd)
375  {
376  Vsingle2 = V_total[nd + 1];
377  hsingle1 = hes[nd];
378 
380  {
381  tmp1 = V_total[nd] + nDir;
382  tmp2 = Vsingle1 + nDir;
383  m_operator.DoNekSysPrecon(tmp1, tmp2);
384  }
385  else
386  {
387  Vsingle1 = V_total[nd];
388  }
389  // w here is no need to add nDir due to temporary Array
390  idtem = id[nd];
391  starttem = id_start[idtem];
392  endtem = id_end[idtem];
393 
394  DoArnoldi(starttem, endtem, nGlobal, nDir, V_total, Vsingle1, Vsingle2,
395  hsingle1);
396 
397  if (starttem > 0)
398  {
399  starttem = starttem - 1;
400  }
401 
402  hsingle2 = Upper[nd];
403  Vmath::Vcopy(m_LinSysMaxStorage + 1, &hsingle1[0], 1, &hsingle2[0], 1);
404  DoGivensRotation(starttem, endtem, nGlobal, nDir, cs, sn, hsingle2,
405  eta);
406 
407  eps = eta[nd + 1] * eta[nd + 1];
408  // This Gmres merge truncted Gmres to accelerate.
409  // If truncted, cannot jump out because
410  // the last term of eta is not residual
411  if ((!truncted) || (nd < m_KrylovMaxHessMatBand))
412  {
413  // If (eps * m_prec_factor < m_tolerance *
414  // m_tolerance * m_rhs_magnitude )
415  if ((eps < m_tolerance * m_tolerance * m_rhs_magnitude) && nd > 0)
416  {
417  m_converged = true;
418  }
419  NekDouble tolmin = 1.0E-15;
420  if (eps < tolmin * tolmin * m_rhs_magnitude)
421  {
422  m_converged = true;
423  }
424  }
425  nswp++;
427 
428  if (m_converged)
429  {
430  break;
431  }
432  }
433 
434  DoBackward(nswp, Upper, eta, y_total);
435  // calculate output y_total*V_total
436  Array<OneD, NekDouble> solution(nGlobal, 0.0);
437  for (int i = 0; i < nswp; ++i)
438  {
439  beta = y_total[i];
440  Vmath::Svtvp(nNonDir, beta, &V_total[i][0] + nDir, 1,
441  &solution[0] + nDir, 1, &solution[0] + nDir, 1);
442  }
443 
445  {
446  tmp1 = solution + nDir;
447  tmp2 = solution + nDir;
448  m_operator.DoNekSysPrecon(tmp1, tmp2);
449  }
450  Vmath::Vadd(nNonDir, &solution[0] + nDir, 1, &pOutput[0] + nDir, 1,
451  &pOutput[0] + nDir, 1);
452 
453  return eps;
454 }
455 
456 // Arnoldi Subroutine
457 void NekLinSysIterGMRES::DoArnoldi(const int starttem, const int endtem,
458  const int nGlobal, const int nDir,
459  // V_total(:,1:nd)
460  Array<OneD, Array<OneD, NekDouble>> &V_local,
461  // V[nd]
462  Array<OneD, NekDouble> &Vsingle1,
463  // V[nd+1]
464  Array<OneD, NekDouble> &Vsingle2,
465  // h
466  Array<OneD, NekDouble> &hsingle)
467 {
468  // To notice, V_local's order not certainly equal to starttem:endtem
469  // starttem:endtem is the entry position in Hessenburg matrix
470  NekDouble alpha, beta;
471  Array<OneD, NekDouble> tmp1, tmp2;
472  int numbertem;
473  int nNonDir = nGlobal - nDir;
474  // Later for parallel
475  NekDouble vExchange = 0.0;
476  // w=AV(:,nd)
477  Array<OneD, NekDouble> w(nGlobal, 0.0);
478 
479  LibUtilities::Timer timer;
480  timer.Start();
482  timer.Stop();
483  timer.AccumulateRegion("NekSysOperators::DoNekSysLhsEval", 10);
484 
485  tmp1 = w + nDir;
486  tmp2 = w + nDir;
488  {
489  m_operator.DoNekSysPrecon(tmp1, tmp2);
490  }
491 
492  Vmath::Smul(nNonDir, sqrt(m_prec_factor), tmp2, 1, tmp2, 1);
493 
494  // Modified Gram-Schmidt
495  // The pointer not certainly equal to starttem.
496  // Like initially, Gmres-deep need to use numbertem=0
497  numbertem = starttem;
498  for (int i = starttem; i < endtem; ++i)
499  {
500  vExchange =
501  Vmath::Dot2(nNonDir, &w[0] + nDir, &V_local[numbertem][0] + nDir,
502  &m_map[0] + nDir);
503  m_Comm->AllReduce(vExchange, LibUtilities::ReduceSum);
504 
505  hsingle[i] = vExchange;
506 
507  beta = -1.0 * vExchange;
508  Vmath::Svtvp(nNonDir, beta, &V_local[numbertem][0] + nDir, 1,
509  &w[0] + nDir, 1, &w[0] + nDir, 1);
510  numbertem = numbertem + 1;
511  }
512  // end of Modified Gram-Schmidt
513 
514  // calculate the L2 norm and normalize
515  vExchange =
516  Vmath::Dot2(nNonDir, &w[0] + nDir, &w[0] + nDir, &m_map[0] + nDir);
517 
518  m_Comm->AllReduce(vExchange, LibUtilities::ReduceSum);
519 
520  hsingle[endtem] = sqrt(vExchange);
521 
522  alpha = 1.0 / hsingle[endtem];
523  Vmath::Smul(nNonDir, alpha, &w[0] + nDir, 1, &Vsingle2[0] + nDir, 1);
524 }
525 
526 // QR factorization through Givens rotation
527 void NekLinSysIterGMRES::DoGivensRotation(const int starttem, const int endtem,
528  const int nGlobal, const int nDir,
531  Array<OneD, NekDouble> &hsingle,
533 {
534  boost::ignore_unused(nGlobal, nDir);
535  NekDouble temp_dbl;
536  NekDouble dd;
537  NekDouble hh;
538  int idtem = endtem - 1;
539  // The starttem and endtem are beginning and ending order of Givens rotation
540  // They usually equal to the beginning position and ending position of
541  // Hessenburg matrix But sometimes starttem will change, like if it is
542  // initial 0 and becomes nonzero because previous Givens rotation See Yu
543  // Pan's User Guide
544  for (int i = starttem; i < idtem; ++i)
545  {
546  temp_dbl = c[i] * hsingle[i] - s[i] * hsingle[i + 1];
547  hsingle[i + 1] = s[i] * hsingle[i] + c[i] * hsingle[i + 1];
548  hsingle[i] = temp_dbl;
549  }
550  dd = hsingle[idtem];
551  hh = hsingle[endtem];
552  if (hh == 0.0)
553  {
554  c[idtem] = 1.0;
555  s[idtem] = 0.0;
556  }
557  else if (abs(hh) > abs(dd))
558  {
559  temp_dbl = -dd / hh;
560  s[idtem] = 1.0 / sqrt(1.0 + temp_dbl * temp_dbl);
561  c[idtem] = temp_dbl * s[idtem];
562  }
563  else
564  {
565  temp_dbl = -hh / dd;
566  c[idtem] = 1.0 / sqrt(1.0 + temp_dbl * temp_dbl);
567  s[idtem] = temp_dbl * c[idtem];
568  }
569 
570  hsingle[idtem] = c[idtem] * hsingle[idtem] - s[idtem] * hsingle[endtem];
571  hsingle[endtem] = 0.0;
572 
573  temp_dbl = c[idtem] * eta[idtem] - s[idtem] * eta[endtem];
574  eta[endtem] = s[idtem] * eta[idtem] + c[idtem] * eta[endtem];
575  eta[idtem] = temp_dbl;
576 }
577 
578 // Backward calculation
579 // To notice, Hesssenburg matrix's column
580 // and row changes due to use Array<OneD,Array<OneD,NekDouble>>
581 void NekLinSysIterGMRES::DoBackward(const int number,
585 {
586  // Number is the entry number
587  // but C++'s order need to be one smaller
588  int maxid = number - 1;
589  NekDouble sum;
590  y[maxid] = b[maxid] / A[maxid][maxid];
591 
592  for (int i = maxid - 1; i > -1; --i)
593  {
594  sum = b[i];
595 
596  for (int j = i + 1; j < number; ++j)
597  {
598  // i and j changes due to use Array<OneD,Array<OneD,NekDouble>>
599  sum = sum - y[j] * A[j][i];
600  }
601  y[i] = sum / A[i][i];
602  }
603 }
604 } // namespace LibUtilities
605 } // namespace Nektar
#define WARNINGL1(condition, msg)
Definition: ErrorUtil.hpp:250
tKey RegisterCreatorFunction(tKey idKey, CreatorFunction classCreator, std::string pDesc="")
Register a class with the factory.
Definition: NekFactory.hpp:198
void DoArnoldi(const int starttem, const int endtem, const int nGlobal, const int nDir, Array< OneD, Array< OneD, NekDouble >> &V_local, Array< OneD, NekDouble > &Vsingle1, Array< OneD, NekDouble > &Vsingle2, Array< OneD, NekDouble > &hsingle)
int DoGMRES(const int pNumRows, const Array< OneD, const NekDouble > &pInput, Array< OneD, NekDouble > &pOutput, const int pNumDir)
Actual iterative solve-GMRES.
void DoBackward(const int number, Array< OneD, Array< OneD, NekDouble >> &A, const Array< OneD, const NekDouble > &b, Array< OneD, NekDouble > &y)
virtual int v_SolveSystem(const int nGlobal, const Array< OneD, const NekDouble > &pInput, Array< OneD, NekDouble > &pOutput, const int nDir, const NekDouble tol, const NekDouble factor) override
void DoGivensRotation(const int starttem, const int endtem, const int nGlobal, const int nDir, Array< OneD, NekDouble > &c, Array< OneD, NekDouble > &s, Array< OneD, NekDouble > &hsingle, Array< OneD, NekDouble > &eta)
NekDouble DoGmresRestart(const bool restarted, const bool truncted, const int nGlobal, const Array< OneD, const NekDouble > &pInput, Array< OneD, NekDouble > &pOutput, const int nDir)
Actual iterative gmres solver for one restart.
virtual void v_InitObject() override
NekDouble m_rhs_magnitude
Dot product of rhs to normalise stopping criterion.
void Set_Rhs_Magnitude(const NekVector< NekDouble > &pIn)
Array< OneD, int > m_map
Global to universal unique map.
bool m_root
Root if parallel.
Definition: NekSys.h:284
bool m_verbose
Verbose.
Definition: NekSys.h:286
NekDouble m_tolerance
Tolerance of iterative solver.
Definition: NekSys.h:278
NekSysOperators m_operator
Operators.
Definition: NekSys.h:289
LibUtilities::CommSharedPtr m_Comm
Communicate.
Definition: NekSys.h:280
bool m_converged
Whether the iteration has been converged.
Definition: NekSys.h:282
int m_maxiter
Maximum iterations.
Definition: NekSys.h:276
void DoNekSysPrecon(InArrayType &inarray, OutArrayType &outarray, const bool &flag=false) const
Definition: NekSys.h:142
void DoNekSysLhsEval(InArrayType &inarray, OutArrayType &outarray, const bool &flag=false) const
Definition: NekSys.h:135
void AccumulateRegion(std::string, int iolevel=0)
Accumulate elapsed time for a region.
Definition: Timer.cpp:73
std::shared_ptr< SessionReader > SessionReaderSharedPtr
NekLinSysIterFactory & GetNekLinSysIterFactory()
@ beta
Gauss Radau pinned at x=-1,.
Definition: PointsType.h:61
std::shared_ptr< Comm > CommSharedPtr
Pointer to a Communicator object.
Definition: Comm.h:54
static const NekDouble kNekUnsetDouble
The above copyright notice and this permission notice shall be included.
Definition: CoupledSolver.h:2
double NekDouble
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:622
T Dot2(int n, const T *w, const T *x, const int *y)
dot2 (vector times vector times vector): z = w*x*y
Definition: Vmath.cpp:1147
void Vadd(int n, const T *x, const int incx, const T *y, const int incy, T *z, const int incz)
Add vector z = x+y.
Definition: Vmath.cpp:359
void Smul(int n, const T alpha, const T *x, const int incx, T *y, const int incy)
Scalar multiply y = alpha*x.
Definition: Vmath.cpp:248
void Zero(int n, T *x, const int incx)
Zero vector.
Definition: Vmath.cpp:492
void Vcopy(int n, const T *x, const int incx, T *y, const int incy)
Definition: Vmath.cpp:1255
scalarT< T > abs(scalarT< T > in)
Definition: scalar.hpp:298
scalarT< T > sqrt(scalarT< T > in)
Definition: scalar.hpp:294