OpenWalnut  1.5.0dev
WTensorFunctions.h
1 //---------------------------------------------------------------------------
2 //
3 // Project: OpenWalnut ( http://www.openwalnut.org )
4 //
5 // Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
6 // For more information see http://www.openwalnut.org/copying
7 //
8 // This file is part of OpenWalnut.
9 //
10 // OpenWalnut is free software: you can redistribute it and/or modify
11 // it under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // OpenWalnut is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
22 //
23 //---------------------------------------------------------------------------
24 
25 #ifndef WTENSORFUNCTIONS_H
26 #define WTENSORFUNCTIONS_H
27 
28 #include <algorithm>
29 #include <cmath>
30 #include <complex>
31 #include <iostream>
32 #include <utility>
33 #include <vector>
34 
35 #include <boost/array.hpp>
36 
37 #include <Eigen/Dense>
38 
39 #include "../WAssert.h"
40 #include "../WLimits.h"
41 #include "WCompileTimeFunctions.h"
42 #include "WTensor.h"
43 #include "WTensorSym.h"
44 #include "WMatrix.h"
45 
46 /**
47  * An eigensystem has all eigenvalues as well as its corresponding eigenvectors. A RealEigenSystem is an EigenSystem where all
48  * eigenvalues are real and not complex.
49  */
50 typedef boost::array< std::pair< double, WVector3d >, 3 > RealEigenSystem;
51 
52 /**
53  * An eigensystem has all eigenvalues as well its corresponding eigenvectors.
54  */
55 typedef boost::array< std::pair< std::complex< double >, WVector3d >, 3 > EigenSystem;
56 
57 std::ostream& operator<<( std::ostream& os, const RealEigenSystem& sys );
58 
59 /**
60  * Helper function to sort eigen values on their size. As each eigenvalues has an eigenvector they must be twiddled the same.
61  *
62  * \param es Eigensystem consisting of eigenvalues and eigenvectors.
63  */
64 inline void sortRealEigenSystem( RealEigenSystem* es )
65 {
66  if( ( *es )[0].first > ( *es )[2].first )
67  {
68  std::swap( ( *es )[0], ( *es )[2] );
69  }
70  if( ( *es )[0].first > ( *es )[1].first )
71  {
72  std::swap( ( *es )[0], ( *es )[1] );
73  }
74  if( ( *es )[1].first > ( *es )[2].first )
75  {
76  std::swap( ( *es )[1], ( *es )[2] );
77  }
78 }
79 
80 /**
81  * Compute all eigenvalues as well as the corresponding eigenvectors of a
82  * symmetric real Matrix.
83  *
84  * \note Data_T must be castable to double.
85  *
86  * \param[in] mat A real symmetric matrix.
87  * \param[out] RealEigenSystem A pointer to an RealEigenSystem.
88  */
89 template< typename Data_T >
90 void jacobiEigenvector3D( WTensorSym< 2, 3, Data_T > const& mat, RealEigenSystem* es )
91 {
92  RealEigenSystem& result = *es; // alias for the result
95  ev( 0, 0 ) = ev( 1, 1 ) = ev( 2, 2 ) = 1.0;
96 
97  int iter = 50;
98  Data_T evp[ 3 ];
99  Data_T evq[ 3 ];
100 
101  while( iter >= 0 )
102  {
103  int p = 1;
104  int q = 0;
105 
106  for( int i = 0; i < 2; ++i )
107  {
108  if( fabs( in( 2, i ) ) > fabs( in( p, q ) ) )
109  {
110  p = 2;
111  q = i;
112  }
113  }
114 
115  // Note: If all non diagonal elements sum up to nearly zero, we may quit already!
116  // Thereby the chosen threshold 1.0e-50 was taken arbitrarily and is just a guess.
117  if( std::abs( in( 0, 1 ) ) + std::abs( in( 0, 2 ) ) + std::abs( in( 1, 2 ) ) < 1.0e-50 )
118  {
119  for( int i = 0; i < 3; ++i )
120  {
121  result[i].first = in( i, i );
122  for( int j = 0; j < 3; ++j )
123  {
124  result[i].second[j] = static_cast< double >( ev( j, i ) );
125  }
126  }
127  sortRealEigenSystem( es );
128  return;
129  }
130 
131  Data_T r = in( q, q ) - in( p, p );
132  Data_T o = r / ( 2.0 * in( p, q ) );
133 
134  Data_T t;
135  Data_T signofo = ( o < 0.0 ? -1.0 : 1.0 );
136  if( o * o > wlimits::MAX_DOUBLE )
137  {
138  t = signofo / ( 2.0 * fabs( o ) );
139  }
140  else
141  {
142  t = signofo / ( fabs( o ) + sqrt( o * o + 1.0 ) );
143  }
144 
145  Data_T c;
146 
147  if( t * t < wlimits::DBL_EPS )
148  {
149  c = 1.0;
150  }
151  else
152  {
153  c = 1.0 / sqrt( t * t + 1.0 );
154  }
155 
156  Data_T s = c * t;
157 
158  int k = 0;
159  while( k == q || k == p )
160  {
161  ++k;
162  }
163 
164  Data_T u = ( 1.0 - c ) / s;
165 
166  Data_T x = in( k, p );
167  Data_T y = in( k, q );
168  in( p, k ) = in( k, p ) = x - s * ( y + u * x );
169  in( q, k ) = in( k, q ) = y + s * ( x - u * y );
170  x = in( p, p );
171  y = in( q, q );
172  in( p, p ) = x - t * in( p, q );
173  in( q, q ) = y + t * in( p, q );
174  in( q, p ) = in( p, q ) = 0.0;
175 
176  for( int l = 0; l < 3; ++l )
177  {
178  evp[ l ] = ev( l, p );
179  evq[ l ] = ev( l, q );
180  }
181  for( int l = 0; l < 3; ++l )
182  {
183  ev( l, p ) = c * evp[ l ] - s * evq[ l ];
184  ev( l, q ) = s * evp[ l ] + c * evq[ l ];
185  }
186 
187  --iter;
188  }
189  WAssert( iter >= 0, "Jacobi eigenvector iteration did not converge." );
190 }
191 
192 /**
193  * Compute all eigenvalues as well as the corresponding eigenvectors of a
194  * symmetric real Matrix.
195  *
196  * \note Data_T must be castable to double.
197  *
198  * \param[in] mat A real symmetric matrix.
199  * \param[out] RealEigenSystem A pointer to an RealEigenSystem.
200  */
201 template< typename Data_T >
202 void eigenEigenvector3D( WTensorSym< 2, 3, Data_T > const& mat, RealEigenSystem* es )
203 {
204  RealEigenSystem& result = *es; // alias for the result
205  typedef Eigen::Matrix< Data_T, 3, 3 > MatrixType;
206 
207  MatrixType m;
208  m << mat( 0, 0 ), mat( 0, 1 ), mat( 0, 2 ),
209  mat( 1, 0 ), mat( 1, 1 ), mat( 1, 2 ),
210  mat( 2, 0 ), mat( 2, 1 ), mat( 2, 2 );
211 
212  Eigen::SelfAdjointEigenSolver< MatrixType > solv( m );
213 
214  for( std::size_t k = 0; k < 3; ++k )
215  {
216  result[ k ].first = solv.eigenvalues()[ k ];
217  for( std::size_t j = 0; j < 3; ++j )
218  {
219  result[ k ].second[ j ] = solv.eigenvectors()( j, k );
220  }
221  }
222 }
223 
224 /**
225  * Calculate eigenvectors via the characteristic polynomial. This is essentially the same
226  * function as in the GPU glyph shaders. This is for 3 dimensions only.
227  *
228  * \param m The symmetric matrix to calculate the eigenvalues from.
229  * \return A std::vector of 3 eigenvalues in descending order (of their magnitude).
230  */
231 std::vector< double > getEigenvaluesCardano( WTensorSym< 2, 3 > const& m );
232 
233 /**
234  * Multiply tensors of order 2. This is essentially a matrix-matrix multiplication.
235  *
236  * Tensors must have the same data type and dimension, however both symmetric and asymmetric
237  * tensors (in any combination) are allowed as operands. The returned tensor is always an asymmetric tensor.
238  *
239  * \param one The first operand, a WTensor or WTensorSym of order 2.
240  * \param other The second operand, a WTensor or WTensorSym of order 2.
241  *
242  * \return A WTensor that is the product of the operands.
243  */
244 template< template< std::size_t, std::size_t, typename > class TensorType1, // NOLINT brainlint can't find TensorType1
245  template< std::size_t, std::size_t, typename > class TensorType2, // NOLINT
246  std::size_t dim,
247  typename Data_T >
248 WTensor< 2, dim, Data_T > operator * ( TensorType1< 2, dim, Data_T > const& one,
249  TensorType2< 2, dim, Data_T > const& other )
250 {
252 
253  // calc res_ij = one_ik * other_kj
254  for( std::size_t i = 0; i < dim; ++i )
255  {
256  for( std::size_t j = 0; j < dim; ++j )
257  {
258  // components are initialized to zero, so there is no need to zero them here
259  for( std::size_t k = 0; k < dim; ++k )
260  {
261  res( i, j ) += one( i, k ) * other( k, j );
262  }
263  }
264  }
265 
266  return res;
267 }
268 
269 /**
270  * Evaluate a spherical function represented by a symmetric 4th-order tensor for a given gradient.
271  *
272  * \tparam Data_T The integral type used to store the tensor elements.
273  *
274  * \param tens The tensor representing the spherical function.
275  * \param gradient The normalized vector that represents the gradient direction.
276  *
277  * \note If the gradient is not normalized, the result is undefined.
278  */
279 template< typename Data_T >
280 double evaluateSphericalFunction( WTensorSym< 4, 3, Data_T > const& tens, WVector3d const& gradient )
281 {
282  // use symmetry to reduce computation overhead
283  // temporaries for some of the gradient element multiplications could further reduce
284  // computation time
285  return gradient[ 0 ] * gradient[ 0 ] * gradient[ 0 ] * gradient[ 0 ] * tens( 0, 0, 0, 0 )
286  + gradient[ 1 ] * gradient[ 1 ] * gradient[ 1 ] * gradient[ 1 ] * tens( 1, 1, 1, 1 )
287  + gradient[ 2 ] * gradient[ 2 ] * gradient[ 2 ] * gradient[ 2 ] * tens( 2, 2, 2, 2 )
288  + static_cast< Data_T >( 4 ) *
289  ( gradient[ 0 ] * gradient[ 0 ] * gradient[ 0 ] * gradient[ 1 ] * tens( 0, 0, 0, 1 )
290  + gradient[ 0 ] * gradient[ 0 ] * gradient[ 0 ] * gradient[ 2 ] * tens( 0, 0, 0, 2 )
291  + gradient[ 1 ] * gradient[ 1 ] * gradient[ 1 ] * gradient[ 0 ] * tens( 1, 1, 1, 0 )
292  + gradient[ 2 ] * gradient[ 2 ] * gradient[ 2 ] * gradient[ 0 ] * tens( 2, 2, 2, 0 )
293  + gradient[ 1 ] * gradient[ 1 ] * gradient[ 1 ] * gradient[ 2 ] * tens( 1, 1, 1, 2 )
294  + gradient[ 2 ] * gradient[ 2 ] * gradient[ 2 ] * gradient[ 1 ] * tens( 2, 2, 2, 1 ) )
295  + static_cast< Data_T >( 12 ) *
296  ( gradient[ 2 ] * gradient[ 1 ] * gradient[ 0 ] * gradient[ 0 ] * tens( 2, 1, 0, 0 )
297  + gradient[ 0 ] * gradient[ 2 ] * gradient[ 1 ] * gradient[ 1 ] * tens( 0, 2, 1, 1 )
298  + gradient[ 0 ] * gradient[ 1 ] * gradient[ 2 ] * gradient[ 2 ] * tens( 0, 1, 2, 2 ) )
299  + static_cast< Data_T >( 6 ) *
300  ( gradient[ 0 ] * gradient[ 0 ] * gradient[ 1 ] * gradient[ 1 ] * tens( 0, 0, 1, 1 )
301  + gradient[ 0 ] * gradient[ 0 ] * gradient[ 2 ] * gradient[ 2 ] * tens( 0, 0, 2, 2 )
302  + gradient[ 1 ] * gradient[ 1 ] * gradient[ 2 ] * gradient[ 2 ] * tens( 1, 1, 2, 2 ) );
303 }
304 
305 /**
306  * Evaluate a spherical function represented by a symmetric 2nd-order tensor for a given gradient.
307  *
308  * \tparam Data_T The integral type used to store the tensor elements.
309  *
310  * \param tens The tensor representing the spherical function.
311  * \param gradient The normalized vector that represents the gradient direction.
312  *
313  * \note If the gradient is not normalized, the result is undefined.
314  */
315 template< typename Data_T >
316 double evaluateSphericalFunction( WTensorSym< 2, 3, Data_T > const& tens, WVector3d const& gradient )
317 {
318  return gradient[ 0 ] * gradient[ 0 ] * tens( 0, 0 )
319  + gradient[ 1 ] * gradient[ 1 ] * tens( 1, 1 )
320  + gradient[ 2 ] * gradient[ 2 ] * tens( 2, 2 )
321  + static_cast< Data_T >( 2 ) *
322  ( gradient[ 0 ] * gradient[ 1 ] * tens( 0, 1 )
323  + gradient[ 0 ] * gradient[ 2 ] * tens( 0, 2 )
324  + gradient[ 1 ] * gradient[ 2 ] * tens( 1, 2 ) );
325 }
326 
327 /**
328  * Calculate the logarithm of the given real symmetric tensor.
329  *
330  * \param tens The tensor.
331  * \return The logarithm of the tensor.
332  */
333 template< typename T >
334 WTensorSym< 2, 3, T > tensorLog( WTensorSym< 2, 3, T > const& tens )
335 {
337 
338  // calculate eigenvalues and eigenvectors
339  RealEigenSystem sys;
340  eigenEigenvector3D( tens, &sys );
341 
342  // first, we check for negative or zero eigenvalues
343  if( sys[ 0 ].first <= 0.0 || sys[ 1 ].first <= 0.0 || sys[ 2 ].first <= 0.0 )
344  {
345  res( 0, 0 ) = res( 1, 1 ) = res( 2, 2 ) = 1.0;
346  return res;
347  }
348 
349  // this implements the matrix product U * log( E ) * U.transposed()
350  // note that u( i, j ) = jth value of the ith eigenvector
351  res( 0, 0 ) = sys[ 0 ].second[ 0 ] * sys[ 0 ].second[ 0 ] * log( sys[ 0 ].first )
352  + sys[ 1 ].second[ 0 ] * sys[ 1 ].second[ 0 ] * log( sys[ 1 ].first )
353  + sys[ 2 ].second[ 0 ] * sys[ 2 ].second[ 0 ] * log( sys[ 2 ].first );
354  res( 0, 1 ) = sys[ 0 ].second[ 0 ] * sys[ 0 ].second[ 1 ] * log( sys[ 0 ].first )
355  + sys[ 1 ].second[ 0 ] * sys[ 1 ].second[ 1 ] * log( sys[ 1 ].first )
356  + sys[ 2 ].second[ 0 ] * sys[ 2 ].second[ 1 ] * log( sys[ 2 ].first );
357  res( 0, 2 ) = sys[ 0 ].second[ 0 ] * sys[ 0 ].second[ 2 ] * log( sys[ 0 ].first )
358  + sys[ 1 ].second[ 0 ] * sys[ 1 ].second[ 2 ] * log( sys[ 1 ].first )
359  + sys[ 2 ].second[ 0 ] * sys[ 2 ].second[ 2 ] * log( sys[ 2 ].first );
360  res( 1, 1 ) = sys[ 0 ].second[ 1 ] * sys[ 0 ].second[ 1 ] * log( sys[ 0 ].first )
361  + sys[ 1 ].second[ 1 ] * sys[ 1 ].second[ 1 ] * log( sys[ 1 ].first )
362  + sys[ 2 ].second[ 1 ] * sys[ 2 ].second[ 1 ] * log( sys[ 2 ].first );
363  res( 1, 2 ) = sys[ 0 ].second[ 1 ] * sys[ 0 ].second[ 2 ] * log( sys[ 0 ].first )
364  + sys[ 1 ].second[ 1 ] * sys[ 1 ].second[ 2 ] * log( sys[ 1 ].first )
365  + sys[ 2 ].second[ 1 ] * sys[ 2 ].second[ 2 ] * log( sys[ 2 ].first );
366  res( 2, 2 ) = sys[ 0 ].second[ 2 ] * sys[ 0 ].second[ 2 ] * log( sys[ 0 ].first )
367  + sys[ 1 ].second[ 2 ] * sys[ 1 ].second[ 2 ] * log( sys[ 1 ].first )
368  + sys[ 2 ].second[ 2 ] * sys[ 2 ].second[ 2 ] * log( sys[ 2 ].first );
369  return res;
370 }
371 
372 /**
373  * Calculate the exponential of the given real symmetric tensor.
374  *
375  * \param tens The tensor.
376  * \return The exponential of the tensor.
377  */
378 template< typename T >
379 WTensorSym< 2, 3, T > tensorExp( WTensorSym< 2, 3, T > const& tens )
380 {
382 
383  // calculate eigenvalues and eigenvectors
384  RealEigenSystem sys;
385  eigenEigenvector3D( tens, &sys );
386 
387  // this implements the matrix product U * exp( E ) * U.transposed()
388  // note that u( i, j ) = jth value of the ith eigenvector
389  res( 0, 0 ) = sys[ 0 ].second[ 0 ] * sys[ 0 ].second[ 0 ] * exp( sys[ 0 ].first )
390  + sys[ 1 ].second[ 0 ] * sys[ 1 ].second[ 0 ] * exp( sys[ 1 ].first )
391  + sys[ 2 ].second[ 0 ] * sys[ 2 ].second[ 0 ] * exp( sys[ 2 ].first );
392  res( 0, 1 ) = sys[ 0 ].second[ 0 ] * sys[ 0 ].second[ 1 ] * exp( sys[ 0 ].first )
393  + sys[ 1 ].second[ 0 ] * sys[ 1 ].second[ 1 ] * exp( sys[ 1 ].first )
394  + sys[ 2 ].second[ 0 ] * sys[ 2 ].second[ 1 ] * exp( sys[ 2 ].first );
395  res( 0, 2 ) = sys[ 0 ].second[ 0 ] * sys[ 0 ].second[ 2 ] * exp( sys[ 0 ].first )
396  + sys[ 1 ].second[ 0 ] * sys[ 1 ].second[ 2 ] * exp( sys[ 1 ].first )
397  + sys[ 2 ].second[ 0 ] * sys[ 2 ].second[ 2 ] * exp( sys[ 2 ].first );
398  res( 1, 1 ) = sys[ 0 ].second[ 1 ] * sys[ 0 ].second[ 1 ] * exp( sys[ 0 ].first )
399  + sys[ 1 ].second[ 1 ] * sys[ 1 ].second[ 1 ] * exp( sys[ 1 ].first )
400  + sys[ 2 ].second[ 1 ] * sys[ 2 ].second[ 1 ] * exp( sys[ 2 ].first );
401  res( 1, 2 ) = sys[ 0 ].second[ 1 ] * sys[ 0 ].second[ 2 ] * exp( sys[ 0 ].first )
402  + sys[ 1 ].second[ 1 ] * sys[ 1 ].second[ 2 ] * exp( sys[ 1 ].first )
403  + sys[ 2 ].second[ 1 ] * sys[ 2 ].second[ 2 ] * exp( sys[ 2 ].first );
404  res( 2, 2 ) = sys[ 0 ].second[ 2 ] * sys[ 0 ].second[ 2 ] * exp( sys[ 0 ].first )
405  + sys[ 1 ].second[ 2 ] * sys[ 1 ].second[ 2 ] * exp( sys[ 1 ].first )
406  + sys[ 2 ].second[ 2 ] * sys[ 2 ].second[ 2 ] * exp( sys[ 2 ].first );
407  return res;
408 }
409 
410 /**
411  * Find the maxima of a spherical function represented by a symmetric tensor.
412  *
413  *
414  *
415  */
416 template< std::size_t order, typename Data_T >
417 void findSymmetricSphericalFunctionMaxima( WTensorSym< order, 3, Data_T > const& tensor,
418  double threshold, double minAngularSeparationCosine, double stepSize,
419  std::vector< WVector3d > const& startingPoints,
420  std::vector< WVector3d >& maxima ) // NOLINT
421 {
422  std::vector< double > values;
423 
424  std::vector< WVector3d >::const_iterator it;
425  for( it = startingPoints.begin(); it != startingPoints.end(); ++it )
426  {
427  WVector3d position = *it;
428  WVector3d gradient = approximateGradient( tensor, position );
429  int iter;
430 
431  for( iter = 0; iter < 100; ++iter )
432  {
433  WVector3d newPosition = normalize( position + stepSize * gradient );
434  WVector3d newGradient = approximateGradient( tensor, newPosition );
435 
436  double cos = dot( newGradient, gradient );
437  if( cos > 0.985 ) // less then 10 degree
438  {
439  stepSize *= 2.0;
440  }
441  else if( cos < 0.866 ) // more than 30 degree
442  {
443  stepSize *= 0.5;
444  if( stepSize < 0.000001 )
445  {
446  break;
447  }
448  }
449  if( cos > 0.886 ) // less than 30 degree
450  {
451  gradient = newGradient;
452  position = newPosition;
453  }
454  }
455  if( iter != 100 )
456  {
457  double newFuncValue = tensor.evaluateSphericalFunction( position );
458 
459  bool add = true;
460 
461  std::vector< int > m;
462  m.reserve( maxima.size() );
463 
464  for( std::size_t k = 0; k != maxima.size(); ++k )
465  {
466  // find all maxima that are to close to this one
467  if( dot( position, maxima[ k ] ) > minAngularSeparationCosine
468  || dot( maxima[ k ], -1.0 * position ) > minAngularSeparationCosine )
469  {
470  m.push_back( k );
471  if( values[ k ] >= newFuncValue )
472  {
473  add = false;
474  }
475  }
476  }
477  if( add )
478  {
479  maxima.push_back( position );
480  values.push_back( newFuncValue );
481 
482  for( int k = static_cast< int >( m.size() - 1 ); k > 0; --k )
483  {
484  maxima.erase( maxima.begin() + m[ k ] );
485  values.erase( values.begin() + m[ k ] );
486  }
487  }
488  }
489  }
490 
491  // remove maxima that are too small
492  double max = 0;
493  for( std::size_t k = 0; k != maxima.size(); ++k )
494  {
495  if( values[ k ] > max )
496  {
497  max = values[ k ];
498  }
499  }
500 
501  std::size_t k = 0;
502  while( k < maxima.size() )
503  {
504  if( values[ k ] < threshold * max )
505  {
506  maxima.erase( maxima.begin() + k );
507  values.erase( values.begin() + k );
508  }
509  else
510  {
511  ++k;
512  }
513  }
514 }
515 
516 template< std::size_t order, typename Data_T >
517 WVector3d approximateGradient( WTensorSym< order, 3, Data_T > const& tensor, WVector3d const& pos )
518 {
519  WVector3d eXY;
520  WVector3d eZ;
521 
522  if( fabs( fabs( pos[ 2 ] ) - 1.0 ) < 0.001 )
523  {
524  eXY = WVector3d( 1.0, 0.0, 0.0 );
525  eZ = WVector3d( 0.0, 1.0, 0.0 );
526  }
527  else
528  {
529  // this is vectorProduct( z, pos )
530  eXY = normalize( WVector3d( -pos[ 1 ], pos[ 0 ], 0.0 ) );
531  // this is vectorProduct( eXY, pos )
532  eZ = normalize( WVector3d( eXY[ 1 ] * pos[ 2 ], -eXY[ 0 ] * pos[ 2 ], eXY[ 0 ] * pos[ 1 ] - eXY[ 1 ] * pos[ 0 ] ) );
533  }
534 
535  double dXY = ( tensor.evaluateSphericalFunction( normalize( pos + eXY * 0.0001 ) )
536  - tensor.evaluateSphericalFunction( normalize( pos - eXY * 0.0001 ) ) )
537  / 0.0002;
538  double dZ = ( tensor.evaluateSphericalFunction( normalize( pos + eZ * 0.0001 ) )
539  - tensor.evaluateSphericalFunction( normalize( pos - eZ * 0.0001 ) ) )
540  / 0.0002;
541 
542  // std::sqrt( 1.0 - z² ) = sin( acos( z ) ) = sin( theta ) in spherical coordinates
543  double d = 1.0 - pos[ 2 ] * pos[ 2 ];
544  if( d < 0.0 ) // avoid possible numerical problems
545  {
546  d = 0.0;
547  }
548  WVector3d res = eZ * dZ + eXY * ( dXY / std::sqrt( d ) );
549  return normalize( res );
550 }
551 
552 #endif // WTENSORFUNCTIONS_H
Implements a symmetric tensor that has the same number of components in every direction.
Definition: WTensorSym.h:73
Data_T evaluateSphericalFunction(WValue< Data_T > const &gradient) const
Evaluate - for a given gradient - the spherical function represented by this symmetric tensor.
Definition: WTensorSym.h:152
Implements a tensor that has the same number of components in every direction.
Definition: WTensor.h:79
const double MAX_DOUBLE
Maximum double value.
Definition: WLimits.cpp:31
const double DBL_EPS
Smallest double such: 1.0 + DBL_EPS == 1.0 is still true.
Definition: WLimits.cpp:46