GEOS
WaveSolverUtils.hpp
Go to the documentation of this file.
1 /*
2  * ------------------------------------------------------------------------------------------------------------
3  * SPDX-License-Identifier: LGPL-2.1-only
4  *
5  * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC
6  * Copyright (c) 2018-2024 TotalEnergies
7  * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University
8  * Copyright (c) 2023-2024 Chevron
9  * Copyright (c) 2019- GEOS/GEOSX Contributors
10  * All rights reserved
11  *
12  * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
13  * ------------------------------------------------------------------------------------------------------------
14  */
15 
20 #ifndef GEOS_PHYSICSSOLVERS_WAVEPROPAGATION_WAVESOLVERUTILS_HPP_
21 #define GEOS_PHYSICSSOLVERS_WAVEPROPAGATION_WAVESOLVERUTILS_HPP_
22 
25 #include "LvArray/src/tensorOps.hpp"
26 
27 namespace geos
28 {
29 
31 {
32  static constexpr real64 epsilonLoc = 1e-8;
33 
34  using EXEC_POLICY = parallelDevicePolicy<>;
35  using wsCoordType = real32;
36 
37  enum class DASType : integer
38  {
39  none,
40  dipole,
42  };
43 
44  enum class AttenuationType : integer
45  {
46  none,
47  sls,
48  };
49 
51  static real32 evaluateRicker( real64 const time_n, real32 const f0, real32 const t0, localIndex const order )
52  {
53  real32 const delay = t0 > 0 ? t0 : 1 / f0;
54  real32 pulse = 0.0;
55  real32 const alpha = -pow( f0 * M_PI, 2 );
56  real32 const time_d = time_n - delay;
57  real32 const gaussian = exp( alpha * pow( time_d, 2 ));
58  localIndex const sgn = pow( -1, order + 1 );
59 
60  switch( order )
61  {
62  case 0:
63  pulse = sgn * gaussian;
64  break;
65  case 1:
66  pulse = sgn * (2 * alpha * time_d) * gaussian;
67  break;
68  case 2:
69  pulse = sgn * (2 * alpha + 4 * pow( alpha, 2 ) * pow( time_d, 2 )) * gaussian;
70  break;
71  case 3:
72  pulse = sgn * (12 * pow( alpha, 2 ) * time_d + 8 * pow( alpha, 3 ) * pow( time_d, 3 )) * gaussian;
73  break;
74  case 4:
75  pulse = sgn * (12 * pow( alpha, 2 ) + 48 * pow( alpha, 3 ) * pow( time_d, 2 ) + 16 * pow( alpha, 4 ) * pow( time_d, 4 )) * gaussian;
76  break;
77  default:
78  GEOS_ERROR( "This option is not supported yet, rickerOrder must be in range {0:4}" );
79  }
80 
81  return pulse;
82  }
83 
92  static void initTrace( char const *prefix,
93  string const & name,
94  bool const outputSeismoTrace,
95  localIndex const nReceivers,
96  arrayView1d< localIndex const > const receiverIsLocal )
97  {
98  if( !outputSeismoTrace )
99  return;
100 
101  string const outputDir = OutputBase::getOutputDirectory();
102  RAJA::ReduceSum< ReducePolicy< serialPolicy >, localIndex > count( 0 );
103 
104  forAll< serialPolicy >( nReceivers, [=]( localIndex const ircv )
105  {
106  if( receiverIsLocal[ircv] == 1 )
107  {
108  count += 1;
109  string const fn = joinPath( outputDir, GEOS_FMT( "{}_{}_{:03}.txt", prefix, name, ircv ) );
110  std::ofstream f( fn, std::ios::out | std::ios::trunc );
111  }
112  } );
113 
114  localIndex const total = MpiWrapper::sum( count.get());
115  GEOS_ERROR_IF( nReceivers != total, GEOS_FMT( ": Invalid distribution of receivers: nReceivers={} != MPI::sum={}.", nReceivers, total ));
116  }
117 
130  static void writeSeismoTraceVector( char const *prefix,
131  string const & name,
132  bool const outputSeismoTrace,
133  localIndex const nReceivers,
134  arrayView1d< localIndex const > const receiverIsLocal,
135  localIndex const nsamplesSeismoTrace,
136  arrayView2d< real32 const > const varAtReceiversx,
137  arrayView2d< real32 const > const varAtReceiversy,
138  arrayView2d< real32 const > const varAtReceiversz )
139  {
140  writeSeismoTrace( prefix, name, outputSeismoTrace, nReceivers, receiverIsLocal, nsamplesSeismoTrace, varAtReceiversx );
141  writeSeismoTrace( prefix, name, outputSeismoTrace, nReceivers, receiverIsLocal, nsamplesSeismoTrace, varAtReceiversy );
142  writeSeismoTrace( prefix, name, outputSeismoTrace, nReceivers, receiverIsLocal, nsamplesSeismoTrace, varAtReceiversz );
143  }
144 
155  static void writeSeismoTrace( char const *prefix,
156  string const & name,
157  bool const outputSeismoTrace,
158  localIndex const nReceivers,
159  arrayView1d< localIndex const > const receiverIsLocal,
160  localIndex const nsamplesSeismoTrace,
161  arrayView2d< real32 const > const varAtReceivers )
162  {
163  if( !outputSeismoTrace )
164  return;
165 
166  string const outputDir = OutputBase::getOutputDirectory();
167  forAll< serialPolicy >( nReceivers, [=]( localIndex const ircv )
168  {
169  if( receiverIsLocal[ircv] == 1 )
170  {
171  string const fn = joinPath( outputDir, GEOS_FMT( "{}_{}_{:03}.txt", prefix, name, ircv ) );
172  std::ofstream f( fn, std::ios::app );
173  if( f )
174  {
175  GEOS_LOG_RANK( GEOS_FMT( "Append to seismo trace file {}", fn ) );
176  for( localIndex iSample = 0; iSample < nsamplesSeismoTrace; ++iSample )
177  {
178  // index - time - value
179  f << iSample << " " << varAtReceivers[iSample][nReceivers] << " " << varAtReceivers[iSample][ircv] << std::endl;
180  }
181  f.close();
182  }
183  else
184  {
185  GEOS_WARNING( GEOS_FMT( "Failed to open output file {}", fn ) );
186  }
187  }
188  } );
189  }
190 
206  static void computeSeismoTrace( real64 const time_n,
207  real64 const dt,
208  real64 const timeSeismo,
209  localIndex const iSeismo,
210  arrayView2d< localIndex const > const receiverNodeIds,
211  arrayView2d< real64 const > const receiverConstants,
212  arrayView1d< localIndex const > const receiverIsLocal,
213  arrayView1d< real32 const > const var_np1,
214  arrayView1d< real32 const > const var_n,
215  arrayView2d< real32 > varAtReceivers,
216  arrayView1d< real32 > coeffs = {},
217  bool add = false )
218  {
219  real64 const time_np1 = time_n + dt;
220 
221  real32 const a1 = LvArray::math::abs( dt ) < epsilonLoc ? 1.0 : (time_np1 - timeSeismo) / dt;
222  real32 const a2 = 1.0 - a1;
223 
224  localIndex const nReceivers = receiverConstants.size( 0 );
225  forAll< EXEC_POLICY >( nReceivers, [=] GEOS_HOST_DEVICE ( localIndex const ircv )
226  {
227  if( receiverIsLocal[ircv] > 0 )
228  {
229  real32 vtmp_np1 = 0.0, vtmp_n = 0.0;
230  for( localIndex inode = 0; inode < receiverConstants.size( 1 ); ++inode )
231  {
232  if( receiverNodeIds( ircv, inode ) >= 0 )
233  {
234  vtmp_np1 += var_np1[receiverNodeIds( ircv, inode )] * receiverConstants( ircv, inode );
235  vtmp_n += var_n[receiverNodeIds( ircv, inode )] * receiverConstants( ircv, inode );
236  }
237  }
238  // linear interpolation between the pressure value at time_n and time_{n+1}
239  real32 receiverCoeff = coeffs.size( 0 ) == 0 ? 1.0 : coeffs( ircv );
240  if( add )
241  {
242  varAtReceivers( iSeismo, ircv ) += receiverCoeff * ( a1 * vtmp_n + a2 * vtmp_np1 );
243  }
244  else
245  {
246  varAtReceivers( iSeismo, ircv ) = receiverCoeff * ( a1 * vtmp_n + a2 * vtmp_np1 );
247  }
248  // NOTE: varAtReceivers has size(1) = numReceiversGlobal + 1, this does not OOB
249  // left in the forAll loop for sync issues since the following does not depend on `ircv`
250  varAtReceivers( iSeismo, nReceivers ) = a1 * time_n + a2 * time_np1;
251  }
252  } );
253  }
254 
270  static void compute2dVariableSeismoTrace( real64 const time_n,
271  real64 const dt,
272  localIndex const regionIndex,
273  arrayView1d< localIndex const > const receiverRegion,
274  real64 const timeSeismo,
275  localIndex const iSeismo,
276  arrayView1d< localIndex const > const receiverElem,
277  arrayView2d< real64 const > const receiverConstants,
278  arrayView1d< localIndex const > const receiverIsLocal,
279  arrayView2d< real32 const > const var_np1,
280  arrayView2d< real32 const > const var_n,
281  arrayView2d< real32 > varAtReceivers )
282  {
283  real64 const time_np1 = time_n + dt;
284 
285  real32 const a1 = dt < epsilonLoc ? 1.0 : (time_np1 - timeSeismo) / dt;
286  real32 const a2 = 1.0 - a1;
287 
288  localIndex const nReceivers = receiverConstants.size( 0 );
289 
290  forAll< EXEC_POLICY >( nReceivers, [=] GEOS_HOST_DEVICE ( localIndex const ircv )
291  {
292  if( receiverIsLocal[ircv] == 1 )
293  {
294  if( receiverRegion[ircv] == regionIndex )
295  {
296  real32 vtmp_np1 = 0.0, vtmp_n = 0.0;
297  for( localIndex inode = 0; inode < receiverConstants.size( 1 ); ++inode )
298  {
299  vtmp_np1 += var_np1( receiverElem[ircv], inode ) * receiverConstants( ircv, inode );
300  vtmp_n += var_n( receiverElem[ircv], inode ) * receiverConstants( ircv, inode );
301  }
302  // linear interpolation between the pressure value at time_n and time_{n+1}
303  varAtReceivers( iSeismo, ircv ) = a1 * vtmp_n + a2 * vtmp_np1;
304  // NOTE: varAtReceivers has size(1) = numReceiversGlobal + 1, this does not OOB
305  // left in the forAll loop for sync issues since the following does not depend on `ircv`
306  varAtReceivers( iSeismo, nReceivers ) = a1 * time_n + a2 * time_np1;
307  }
308  }
309  } );
310  }
311 
323  static bool
324  locateSourceElement( real64 const numFacesPerElem,
325  real64 const (&elemCenter)[3],
326  arrayView2d< real64 const > const faceNormal,
327  arrayView2d< real64 const > const faceCenter,
328  arraySlice1d< localIndex const > const elemsToFaces,
329  real64 const (&coords)[3] )
330  {
331  // Loop over the element faces
332  real64 tmpVector[3]{};
333  for( localIndex kfe = 0; kfe < numFacesPerElem; ++kfe )
334  {
335 
336  localIndex const iface = elemsToFaces[kfe];
337  real64 faceCenterOnFace[3] = {faceCenter[iface][0],
338  faceCenter[iface][1],
339  faceCenter[iface][2]};
340  real64 faceNormalOnFace[3] = {faceNormal[iface][0],
341  faceNormal[iface][1],
342  faceNormal[iface][2]};
343 
344  // Test to make sure if the normal is outwardly directed
345  LvArray::tensorOps::copy< 3 >( tmpVector, faceCenterOnFace );
346  LvArray::tensorOps::subtract< 3 >( tmpVector, elemCenter );
347  if( LvArray::tensorOps::AiBi< 3 >( tmpVector, faceNormalOnFace ) < 0.0 )
348  {
349  LvArray::tensorOps::scale< 3 >( faceNormalOnFace, -1 );
350  }
351 
352  // compute the vector face center to query point
353  LvArray::tensorOps::subtract< 3 >( faceCenterOnFace, coords );
354  localIndex const s = computationalGeometry::sign( LvArray::tensorOps::AiBi< 3 >( faceNormalOnFace, faceCenterOnFace ));
355 
356  // all dot products should be non-negative (we enforce outward normals)
357  if( s < 0 )
358  return false;
359  }
360  return true;
361  }
362 
371  template< typename FE_TYPE >
372  GEOS_HOST_DEVICE static void
376  real64 (& coordsOnRefElem)[3] )
377  {
378  // only the eight corners of the mesh cell are needed to compute the Jacobian
379  real64 xLocal[8][3]{};
380  for( localIndex a = 0; a < 8; ++a )
381  {
382  LvArray::tensorOps::copy< 3 >( xLocal[a], nodeCoords[elemsToNodes[a]] );
383  }
384  // coordsOnRefElem = invJ*(coords-coordsNode_0)
385  real64 invJ[3][3]{};
386  FE_TYPE::invJacobianTransformation( 0, xLocal, invJ );
387  for( localIndex i = 0; i < 3; ++i )
388  {
389  // init at (-1,-1,-1) as the origin of the referential elem
390  coordsOnRefElem[i] = -1.0;
391  for( localIndex j = 0; j < 3; ++j )
392  {
393  coordsOnRefElem[i] += invJ[i][j] * (coords[j] - xLocal[0][j]);
394  }
395  }
396  }
397 
409  static void dotProduct( localIndex const size,
410  arrayView1d< real32 > const & vector1,
411  arrayView1d< real32 > const & vector2,
412  real64 & res )
413  {
414 
415  RAJA::ReduceSum< parallelDeviceReduce, real64 > tmp( 0.0 );
416  forAll< EXEC_POLICY >( size, [=] GEOS_HOST_DEVICE ( localIndex const a )
417  { tmp += vector1[a] * vector2[a]; } );
418 
419  res = tmp.get();
420  }
421 
429  static R1Tensor computeDASVector( real64 const dip, real64 const azimuth )
430  {
431  real64 cd = cos( dip );
432  real64 v1 = cd * cos( azimuth );
433  real64 v2 = cd * sin( azimuth );
434  real64 v3 = sin( dip );
435  R1Tensor dasVector = {v1, v2, v3};
436  return dasVector;
437  }
438 
446  template< typename REAL >
447  GEOS_HOST_DEVICE static REAL
450  {
451  // Loop over the element faces
452  REAL hs = 0;
453  REAL spxf[3][2]{};
454  REAL v1[3]{};
455  REAL v2[3]{};
456  REAL spx[3][3]{};
457  for( int i = 0; i < 4; i++ )
458  {
459  REAL cross[3]{};
460  int cf = 0;
461  int jstart = (i + 1) % 4;
462  int j = (jstart + 1) % 4;
463  do
464  {
465  for( int k = 0; k < 3; k++ )
466  {
467  spxf[k][cf] = nodeCoords( elemsToNodes[j], k ) - nodeCoords( elemsToNodes[jstart], k );
468  }
469  j = (j + 1) % 4;
470  cf++;
471  }
472  while (i != j);
473 
474  v1[0] = spxf[0][0];
475  v1[1] = spxf[1][0];
476  v1[2] = spxf[2][0];
477  v2[0] = spxf[0][1];
478  v2[1] = spxf[1][1];
479  v2[2] = spxf[2][1];
480  LvArray::tensorOps::crossProduct( cross, v1, v2 );
481  hs = hs + LvArray::math::sqrt( LvArray::tensorOps::l2NormSquared< 3 >( cross ));
482  }
483 
484  for( int i = 0; i < 3; i++ )
485  {
486  for( int k = 0; k < 3; k++ )
487  {
488  spx[i][k] = nodeCoords( elemsToNodes[i], k ) - nodeCoords( elemsToNodes[3], k );
489  }
490  }
491 
492 
493  return LvArray::math::abs( LvArray::tensorOps::determinant< 3 >( spx ) / hs );
494  }
495 };
496 
499  "none",
500  "dipole",
501  "strainIntegration" );
502 
504  "none",
505  "sls" );
506 
507 } /* namespace geos */
508 
509 #endif /* GEOS_PHYSICSSOLVERS_WAVEPROPAGATION_WAVESOLVERUTILS_HPP_ */
#define GEOS_HOST_DEVICE
Marks a host-device function.
Definition: GeosxMacros.hpp:49
#define GEOS_LOG_RANK(msg)
Log a message to the rank output stream.
Definition: Logger.hpp:126
#define GEOS_ERROR(msg)
Raise a hard error and terminate the program.
Definition: Logger.hpp:157
#define GEOS_WARNING(msg)
Report a warning.
Definition: Logger.hpp:190
#define GEOS_ERROR_IF(EXP, msg)
Conditionally raise a hard error and terminate the program.
Definition: Logger.hpp:142
static string const & getOutputDirectory()
Getter for the output directory.
ArrayView< T, 1 > arrayView1d
Alias for 1D array view.
Definition: DataTypes.hpp:179
float real32
32-bit floating point type.
Definition: DataTypes.hpp:96
double real64
64-bit floating point type.
Definition: DataTypes.hpp:98
GEOS_LOCALINDEX_TYPE localIndex
Local index type (for indexing objects within an MPI partition).
Definition: DataTypes.hpp:84
ArraySlice< T, 1, USD > arraySlice1d
Alias for 1D array slice.
Definition: DataTypes.hpp:183
std::string joinPath(ARGS const &... args)
Join parts of a path.
Definition: Path.hpp:179
ArrayView< T, 2, USD > arrayView2d
Alias for 2D array view.
Definition: DataTypes.hpp:195
int integer
Signed integer type.
Definition: DataTypes.hpp:81
ENUM_STRINGS(LinearSolverParameters::SolverType, "direct", "cg", "gmres", "fgmres", "bicgstab", "richardson", "preconditioner")
Declare strings associated with enumeration values.
static T sum(T const &value, MPI_Comm comm=MPI_COMM_GEOS)
Convenience function for a MPI_Allreduce using a MPI_SUM operation.
static GEOS_HOST_DEVICE void computeCoordinatesOnReferenceElement(real64 const (&coords)[3], arraySlice1d< localIndex const, cells::NODE_MAP_USD - 1 > const elemsToNodes, arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const nodeCoords, real64(&coordsOnRefElem)[3])
Convert a mesh element point coordinate into a coordinate on the reference element.
static void compute2dVariableSeismoTrace(real64 const time_n, real64 const dt, localIndex const regionIndex, arrayView1d< localIndex const > const receiverRegion, real64 const timeSeismo, localIndex const iSeismo, arrayView1d< localIndex const > const receiverElem, arrayView2d< real64 const > const receiverConstants, arrayView1d< localIndex const > const receiverIsLocal, arrayView2d< real32 const > const var_np1, arrayView2d< real32 const > const var_n, arrayView2d< real32 > varAtReceivers)
Compute the seismo traces for 2d arrays.
static void writeSeismoTraceVector(char const *prefix, string const &name, bool const outputSeismoTrace, localIndex const nReceivers, arrayView1d< localIndex const > const receiverIsLocal, localIndex const nsamplesSeismoTrace, arrayView2d< real32 const > const varAtReceiversx, arrayView2d< real32 const > const varAtReceiversy, arrayView2d< real32 const > const varAtReceiversz)
Convenient helper for 3D vectors calling 3 times the scalar version with only the sampled variable ar...
static GEOS_HOST_DEVICE REAL computeReferenceLengthForPenalty(arraySlice1d< localIndex const, cells::NODE_MAP_USD - 1 > const elemsToNodes, arrayView2d< REAL const, nodes::REFERENCE_POSITION_USD > const nodeCoords)
Compute the reference size used for the tetrahederal DG penalty coefficient. This implementation uses...
static GEOS_HOST_DEVICE bool locateSourceElement(real64 const numFacesPerElem, real64 const (&elemCenter)[3], arrayView2d< real64 const > const faceNormal, arrayView2d< real64 const > const faceCenter, arraySlice1d< localIndex const > const elemsToFaces, real64 const (&coords)[3])
Check if the source point is inside an element or not.
static void dotProduct(localIndex const size, arrayView1d< real32 > const &vector1, arrayView1d< real32 > const &vector2, real64 &res)
Compute dotProduct between two vectors.
static void computeSeismoTrace(real64 const time_n, real64 const dt, real64 const timeSeismo, localIndex const iSeismo, arrayView2d< localIndex const > const receiverNodeIds, arrayView2d< real64 const > const receiverConstants, arrayView1d< localIndex const > const receiverIsLocal, arrayView1d< real32 const > const var_np1, arrayView1d< real32 const > const var_n, arrayView2d< real32 > varAtReceivers, arrayView1d< real32 > coeffs={}, bool add=false)
Compute the seismo traces.
@ none
deactivate attenuation (default)
@ sls
istandard-linear-solid description [Fichtner 2014]
static GEOS_HOST_DEVICE R1Tensor computeDASVector(real64 const dip, real64 const azimuth)
Converts the DAS direction from dip/azimuth to a 3D unit vector.
@ none
deactivate DAS computation
@ dipole
use dipole formulation for DAS
@ strainIntegration
use strain integration for DAS
static void writeSeismoTrace(char const *prefix, string const &name, bool const outputSeismoTrace, localIndex const nReceivers, arrayView1d< localIndex const > const receiverIsLocal, localIndex const nsamplesSeismoTrace, arrayView2d< real32 const > const varAtReceivers)
Write the seismo traces to a file.
static void initTrace(char const *prefix, string const &name, bool const outputSeismoTrace, localIndex const nReceivers, arrayView1d< localIndex const > const receiverIsLocal)
Initialize (clear) the trace file.