fullwave25 1.0.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fullwave25 might be problematic. Click here for more details.

Files changed (225) hide show
  1. fullwave/__init__.py +28 -0
  2. fullwave/constants/__init__.py +5 -0
  3. fullwave/constants/material_properties.py +112 -0
  4. fullwave/grid.py +222 -0
  5. fullwave/medium.py +1042 -0
  6. fullwave/medium_builder/__init__.py +12 -0
  7. fullwave/medium_builder/domain.py +151 -0
  8. fullwave/medium_builder/medium_builder.py +198 -0
  9. fullwave/medium_builder/presets/__init__.py +8 -0
  10. fullwave/medium_builder/presets/data/.keep +0 -0
  11. fullwave/medium_builder/presets/data/abdominal_wall/i2365f_etfw1.mat +0 -0
  12. fullwave/medium_builder/presets/domain_abdominal_wall.py +293 -0
  13. fullwave/medium_builder/presets/domain_background.py +140 -0
  14. fullwave/medium_builder/presets/domain_scatterer.py +179 -0
  15. fullwave/medium_builder/presets/domain_simple.py +92 -0
  16. fullwave/medium_builder/presets/domain_water_gel.py +1 -0
  17. fullwave/sensor.py +161 -0
  18. fullwave/solver/__init__.py +1 -0
  19. fullwave/solver/bins/database/relaxation_params_database_num_relax=2_20251027_1437.mat +0 -0
  20. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenu +0 -0
  21. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_100_cuda129 +0 -0
  22. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_101_cuda129 +0 -0
  23. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_120_cuda129 +0 -0
  24. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_61_cuda118 +0 -0
  25. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_61_cuda124 +0 -0
  26. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_61_cuda126 +0 -0
  27. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_61_cuda129 +0 -0
  28. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_70_cuda118 +0 -0
  29. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_70_cuda124 +0 -0
  30. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_70_cuda126 +0 -0
  31. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_70_cuda129 +0 -0
  32. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_75_cuda118 +0 -0
  33. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_75_cuda124 +0 -0
  34. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_75_cuda126 +0 -0
  35. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_75_cuda129 +0 -0
  36. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_80_cuda118 +0 -0
  37. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_80_cuda124 +0 -0
  38. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_80_cuda126 +0 -0
  39. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_80_cuda129 +0 -0
  40. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_86_cuda118 +0 -0
  41. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_86_cuda124 +0 -0
  42. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_86_cuda126 +0 -0
  43. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_86_cuda129 +0 -0
  44. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_89_cuda118 +0 -0
  45. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_89_cuda126 +0 -0
  46. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_89_cuda129 +0 -0
  47. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_90_cuda118 +0 -0
  48. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_90_cuda124 +0 -0
  49. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_90_cuda126 +0 -0
  50. fullwave/solver/bins/exponential_attenuation/gpu/2d/fullwave2_2d_exponential_attenuation_gpu_sm_90_cuda129 +0 -0
  51. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_100_cuda129 +0 -0
  52. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_101_cuda129 +0 -0
  53. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_120_cuda129 +0 -0
  54. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_61_cuda118 +0 -0
  55. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_61_cuda124 +0 -0
  56. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_61_cuda126 +0 -0
  57. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_61_cuda129 +0 -0
  58. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_70_cuda118 +0 -0
  59. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_70_cuda124 +0 -0
  60. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_70_cuda126 +0 -0
  61. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_70_cuda129 +0 -0
  62. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_75_cuda118 +0 -0
  63. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_75_cuda124 +0 -0
  64. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_75_cuda126 +0 -0
  65. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_75_cuda129 +0 -0
  66. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_80_cuda118 +0 -0
  67. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_80_cuda124 +0 -0
  68. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_80_cuda126 +0 -0
  69. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_80_cuda129 +0 -0
  70. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_86_cuda118 +0 -0
  71. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_86_cuda124 +0 -0
  72. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_86_cuda126 +0 -0
  73. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_86_cuda129 +0 -0
  74. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_89_cuda118 +0 -0
  75. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_89_cuda124 +0 -0
  76. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_89_cuda126 +0 -0
  77. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_89_cuda129 +0 -0
  78. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_90_cuda118 +0 -0
  79. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_90_cuda124 +0 -0
  80. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_90_cuda126 +0 -0
  81. fullwave/solver/bins/exponential_attenuation/gpu/3d/fullwave2_3d_exponential_attenuation_gpu_sm_90_cuda129 +0 -0
  82. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_100_cuda129 +0 -0
  83. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_101_cuda129 +0 -0
  84. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_120_cuda129 +0 -0
  85. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_61_cuda118 +0 -0
  86. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_61_cuda124 +0 -0
  87. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_61_cuda126 +0 -0
  88. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_61_cuda129 +0 -0
  89. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_70_cuda118 +0 -0
  90. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_70_cuda124 +0 -0
  91. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_70_cuda126 +0 -0
  92. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_70_cuda129 +0 -0
  93. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_75_cuda118 +0 -0
  94. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_75_cuda124 +0 -0
  95. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_75_cuda126 +0 -0
  96. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_75_cuda129 +0 -0
  97. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_80_cuda118 +0 -0
  98. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_80_cuda124 +0 -0
  99. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_80_cuda126 +0 -0
  100. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_80_cuda129 +0 -0
  101. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_86_cuda118 +0 -0
  102. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_86_cuda124 +0 -0
  103. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_86_cuda126 +0 -0
  104. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_86_cuda129 +0 -0
  105. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_89_cuda118 +0 -0
  106. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_89_cuda124 +0 -0
  107. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_89_cuda126 +0 -0
  108. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_89_cuda129 +0 -0
  109. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_90_cuda118 +0 -0
  110. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_90_cuda124 +0 -0
  111. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_90_cuda126 +0 -0
  112. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_isotropic_multi_gpu_sm_90_cuda129 +0 -0
  113. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_100_cuda129 +0 -0
  114. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_101_cuda129 +0 -0
  115. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_120_cuda129 +0 -0
  116. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_61_cuda118 +0 -0
  117. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_61_cuda124 +0 -0
  118. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_61_cuda126 +0 -0
  119. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_61_cuda129 +0 -0
  120. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_70_cuda118 +0 -0
  121. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_70_cuda124 +0 -0
  122. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_70_cuda126 +0 -0
  123. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_70_cuda129 +0 -0
  124. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_75_cuda118 +0 -0
  125. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_75_cuda124 +0 -0
  126. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_75_cuda126 +0 -0
  127. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_75_cuda129 +0 -0
  128. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_80_cuda118 +0 -0
  129. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_80_cuda124 +0 -0
  130. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_80_cuda126 +0 -0
  131. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_80_cuda129 +0 -0
  132. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_86_cuda118 +0 -0
  133. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_86_cuda124 +0 -0
  134. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_86_cuda126 +0 -0
  135. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_86_cuda129 +0 -0
  136. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_89_cuda118 +0 -0
  137. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_89_cuda124 +0 -0
  138. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_89_cuda126 +0 -0
  139. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_89_cuda129 +0 -0
  140. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_90_cuda118 +0 -0
  141. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_90_cuda124 +0 -0
  142. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_90_cuda126 +0 -0
  143. fullwave/solver/bins/gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_sm_90_cuda129 +0 -0
  144. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_100_cuda129 +0 -0
  145. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_101_cuda129 +0 -0
  146. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_120_cuda129 +0 -0
  147. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_61_cuda118 +0 -0
  148. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_61_cuda124 +0 -0
  149. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_61_cuda126 +0 -0
  150. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_61_cuda129 +0 -0
  151. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_70_cuda118 +0 -0
  152. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_70_cuda124 +0 -0
  153. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_70_cuda126 +0 -0
  154. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_70_cuda129 +0 -0
  155. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_75_cuda118 +0 -0
  156. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_75_cuda124 +0 -0
  157. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_75_cuda126 +0 -0
  158. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_75_cuda129 +0 -0
  159. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_80_cuda118 +0 -0
  160. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_80_cuda124 +0 -0
  161. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_80_cuda126 +0 -0
  162. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_80_cuda129 +0 -0
  163. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_86_cuda118 +0 -0
  164. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_86_cuda124 +0 -0
  165. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_86_cuda126 +0 -0
  166. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_86_cuda129 +0 -0
  167. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_89_cuda118 +0 -0
  168. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_89_cuda124 +0 -0
  169. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_89_cuda126 +0 -0
  170. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_89_cuda129 +0 -0
  171. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_90_cuda118 +0 -0
  172. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_90_cuda124 +0 -0
  173. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_90_cuda126 +0 -0
  174. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_isotropic_multi_gpu_sm_90_cuda129 +0 -0
  175. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_100_cuda129 +0 -0
  176. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_101_cuda129 +0 -0
  177. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_120_cuda129 +0 -0
  178. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_61_cuda118 +0 -0
  179. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_61_cuda124 +0 -0
  180. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_61_cuda126 +0 -0
  181. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_61_cuda129 +0 -0
  182. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_70_cuda118 +0 -0
  183. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_70_cuda124 +0 -0
  184. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_70_cuda126 +0 -0
  185. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_70_cuda129 +0 -0
  186. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_75_cuda118 +0 -0
  187. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_75_cuda124 +0 -0
  188. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_75_cuda126 +0 -0
  189. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_75_cuda129 +0 -0
  190. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_80_cuda118 +0 -0
  191. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_80_cuda124 +0 -0
  192. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_80_cuda126 +0 -0
  193. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_80_cuda129 +0 -0
  194. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_86_cuda118 +0 -0
  195. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_86_cuda124 +0 -0
  196. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_86_cuda126 +0 -0
  197. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_86_cuda129 +0 -0
  198. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_89_cuda118 +0 -0
  199. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_89_cuda124 +0 -0
  200. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_89_cuda126 +0 -0
  201. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_89_cuda129 +0 -0
  202. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_90_cuda118 +0 -0
  203. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_90_cuda124 +0 -0
  204. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_90_cuda126 +0 -0
  205. fullwave/solver/bins/gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_sm_90_cuda129 +0 -0
  206. fullwave/solver/cuda_utils.py +392 -0
  207. fullwave/solver/input_file_writer.py +853 -0
  208. fullwave/solver/launcher.py +134 -0
  209. fullwave/solver/pml_builder.py +1923 -0
  210. fullwave/solver/solver.py +750 -0
  211. fullwave/solver/utils.py +83 -0
  212. fullwave/source.py +173 -0
  213. fullwave/transducer.py +1003 -0
  214. fullwave/utils/__init__.py +12 -0
  215. fullwave/utils/check_functions.py +48 -0
  216. fullwave/utils/coordinates.py +155 -0
  217. fullwave/utils/memory_tempfile.py +439 -0
  218. fullwave/utils/numerical.py +111 -0
  219. fullwave/utils/plot_utils.py +1122 -0
  220. fullwave/utils/pulse.py +72 -0
  221. fullwave/utils/relaxation_parameters.py +212 -0
  222. fullwave/utils/signal_process.py +197 -0
  223. fullwave25-1.0.7.dist-info/METADATA +292 -0
  224. fullwave25-1.0.7.dist-info/RECORD +225 -0
  225. fullwave25-1.0.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,1923 @@
1
+ """Perfectly Matched Layer (PML) setup for Fullwave."""
2
+
3
+ import logging
4
+ from collections import OrderedDict
5
+ from dataclasses import dataclass, field
6
+ from functools import cached_property
7
+ from pathlib import Path
8
+
9
+ import matplotlib.pyplot as plt
10
+ import numpy as np
11
+ from numpy.typing import NDArray
12
+
13
+ import fullwave
14
+ from fullwave.solver.utils import initialize_relaxation_param_dict
15
+ from fullwave.utils import check_functions, plot_utils
16
+
17
+ logger = logging.getLogger("__main__." + __name__)
18
+
19
+
20
+ def _smooth_transition_function_part(x: NDArray[np.float64]) -> NDArray[np.float64]:
21
+ return np.where(x > 0, np.exp(-1 / (x + 1e-20)), 0)
22
+
23
+
24
+ def _smooth_transition_function(x: NDArray[np.float64]) -> NDArray[np.float64]:
25
+ return _smooth_transition_function_part(x) / (
26
+ _smooth_transition_function_part(x) + _smooth_transition_function_part(1 - x)
27
+ )
28
+
29
+
30
+ def _linear_transition_function(x: NDArray[np.float64]) -> NDArray[np.float64]:
31
+ return x
32
+
33
+
34
+ def _n_th_deg_polynomial_function(x: NDArray[np.float64], n: int = 2) -> NDArray[np.float64]:
35
+ return x**n
36
+
37
+
38
+ def _cosine_transition_function(x: NDArray[np.float64]) -> NDArray[np.float64]:
39
+ return 0.5 * (1 - np.cos(np.pi * x))
40
+
41
+
42
+ def _obtain_relax_var_rename_dict(
43
+ n_relaxation_mechanisms: int,
44
+ *,
45
+ is_3d: bool = False,
46
+ use_isotropic_relaxation: bool = False,
47
+ ) -> dict:
48
+ if use_isotropic_relaxation:
49
+ rename_dict = {
50
+ "kappa_x": "kappa_x2",
51
+ "kappa_u": "kappa_x1",
52
+ }
53
+
54
+ for nu in range(1, n_relaxation_mechanisms + 1):
55
+ rename_dict[f"d_u_nu{nu}"] = f"d_x1_nu{nu}"
56
+ rename_dict[f"d_x_nu{nu}"] = f"d_x2_nu{nu}"
57
+
58
+ rename_dict[f"alpha_u_nu{nu}"] = f"alpha_x1_nu{nu}"
59
+ rename_dict[f"alpha_x_nu{nu}"] = f"alpha_x2_nu{nu}"
60
+ else:
61
+ rename_dict = {
62
+ "kappa_x": "kappa_x2",
63
+ "kappa_y": "kappa_x2",
64
+ "kappa_u": "kappa_x1",
65
+ "kappa_w": "kappa_x1",
66
+ }
67
+ if is_3d:
68
+ rename_dict.update(
69
+ {
70
+ "kappa_z": "kappa_x2",
71
+ "kappa_v": "kappa_x1",
72
+ },
73
+ )
74
+ for nu in range(1, n_relaxation_mechanisms + 1):
75
+ rename_dict[f"d_u_nu{nu}"] = f"d_x1_nu{nu}"
76
+ rename_dict[f"d_w_nu{nu}"] = f"d_x1_nu{nu}"
77
+ rename_dict[f"d_x_nu{nu}"] = f"d_x2_nu{nu}"
78
+ rename_dict[f"d_y_nu{nu}"] = f"d_x2_nu{nu}"
79
+
80
+ rename_dict[f"alpha_u_nu{nu}"] = f"alpha_x1_nu{nu}"
81
+ rename_dict[f"alpha_w_nu{nu}"] = f"alpha_x1_nu{nu}"
82
+ rename_dict[f"alpha_x_nu{nu}"] = f"alpha_x2_nu{nu}"
83
+ rename_dict[f"alpha_y_nu{nu}"] = f"alpha_x2_nu{nu}"
84
+ if is_3d:
85
+ rename_dict[f"d_v_nu{nu}"] = f"d_x1_nu{nu}"
86
+ rename_dict[f"d_z_nu{nu}"] = f"d_x2_nu{nu}"
87
+ rename_dict[f"alpha_v_nu{nu}"] = f"alpha_x1_nu{nu}"
88
+ rename_dict[f"alpha_z_nu{nu}"] = f"alpha_x2_nu{nu}"
89
+
90
+ return rename_dict
91
+
92
+
93
+ @dataclass
94
+ class PMLBuilder:
95
+ """Setup for Perfectly Matched Layers (PML) in fullwave simulations."""
96
+
97
+ medium_org: fullwave.Medium
98
+ source_org: fullwave.Source
99
+ sensor_org: fullwave.Sensor
100
+
101
+ m_spatial_order: int
102
+ n_pml_layer: int
103
+ n_relaxation: int
104
+ n_transition_layer: int
105
+
106
+ extended_grid: fullwave.Grid = field(init=False)
107
+ extended_medium: fullwave.Medium = field(init=False)
108
+ extended_source: fullwave.Source = field(init=False)
109
+ extended_sensor: fullwave.Sensor = field(init=False)
110
+
111
+ pml_mask_x: NDArray[np.float64] = field(init=False)
112
+ pml_mask_y: NDArray[np.float64] = field(init=False)
113
+
114
+ def __init__(
115
+ self,
116
+ grid: fullwave.Grid,
117
+ medium: fullwave.Medium,
118
+ source: fullwave.Source,
119
+ sensor: fullwave.Sensor,
120
+ *,
121
+ m_spatial_order: int = 8,
122
+ n_pml_layer: int = 40,
123
+ n_transition_layer: int = 40,
124
+ use_isotropic_relaxation: bool = False,
125
+ # pml_alpha_target: float = 1.1,
126
+ # pml_alpha_power_target: float = 1.6,
127
+ # pml_strength_factor: float = 2.0,
128
+ # use_2_relax_mechanisms: bool = False,
129
+ ) -> None:
130
+ """Initialize the PMLSetup with the given medium, source, sensor, and PML parameters.
131
+
132
+ Parameters
133
+ ----------
134
+ grid: fullwave.Grid
135
+ The grid configuration.
136
+ medium : fullwave.Medium)
137
+ The medium relaxation maps.
138
+ source : fullwave.Source
139
+ The source configuration.
140
+ sensor : fullwave.Sensor
141
+ The sensor configuration.
142
+ m_spatial_order : int, optional
143
+ fullwave simulation's spatial order (default is 8).
144
+ It depends on the fullwave simulation binary version.
145
+ Fullwave simulation has 2M th order spatial accuracy and fourth order accuracy in time.
146
+ see Pinton, G. (2021) http://arxiv.org/abs/2106.11476 for more detail.
147
+ n_pml_layer : int, optional
148
+ PML layer thickness (default is 40).
149
+ n_transition_layer : int, optional
150
+ Number of transition layers (default is 40).
151
+ pml_alpha_target : float, optional
152
+ Target alpha value for PML (default is 0.5).
153
+ This value is used to calculate the transition layer values.
154
+ pml_alpha_power_target : float, optional
155
+ Target alpha power value for PML (default is 1.0).
156
+ This value is used to calculate the transition layer values.
157
+ pml_strength_factor : float, optional
158
+ Strength factor for PML (default is 2.0).
159
+ This value is used to calculate the PML target values.
160
+ use_2_relax_mechanisms : bool, optional
161
+ If True, use 2 relaxation mechanisms for PML for stability (default is False).
162
+ if True, pml_alpha_target, pml_alpha_power_target, and pml_strength_factor are ignored.
163
+ use_isotropic_relaxation : bool, optional
164
+ Whether to use isotropic relaxation mechanisms for attenuation modeling
165
+ to reduce memory usage while retaining accuracy.
166
+ For 2D it will reduce the memory usage by approximately 15%.
167
+ For 3D it will reduce the memory usage by approximately 25%.
168
+ This option omits the anisotropic relaxation mechanisms to model the attenuation.
169
+ We usually recommend using isotropic relaxation mechanisms
170
+ unless the anisotropic attenuation is required for the simulation.
171
+
172
+ """
173
+ check_functions.check_instance(
174
+ grid,
175
+ fullwave.Grid,
176
+ )
177
+ check_functions.check_instance(
178
+ medium,
179
+ fullwave.Medium,
180
+ )
181
+ check_functions.check_instance(
182
+ source,
183
+ fullwave.Source,
184
+ )
185
+ check_functions.check_instance(
186
+ sensor,
187
+ fullwave.Sensor,
188
+ )
189
+
190
+ self.grid_org = grid
191
+ self.medium_org = medium
192
+ self.source_org = source
193
+ self.sensor_org = sensor
194
+ self.is_3d = grid.is_3d
195
+ self.use_isotropic_relaxation = use_isotropic_relaxation
196
+
197
+ self.m_spatial_order = m_spatial_order
198
+ self.n_pml_layer = n_pml_layer
199
+ self.n_transition_layer = n_transition_layer
200
+
201
+ domain_size: tuple[float, ...]
202
+ if self.is_3d:
203
+ domain_size = (
204
+ (self.medium_org.sound_speed.shape[0] + 2 * self.num_boundary_points)
205
+ * self.grid_org.dx,
206
+ (self.medium_org.sound_speed.shape[1] + 2 * self.num_boundary_points)
207
+ * self.grid_org.dy,
208
+ (self.medium_org.sound_speed.shape[2] + 2 * self.num_boundary_points)
209
+ * self.grid_org.dz,
210
+ )
211
+ else:
212
+ domain_size = (
213
+ (self.medium_org.sound_speed.shape[0] + 2 * self.num_boundary_points)
214
+ * self.grid_org.dx,
215
+ (self.medium_org.sound_speed.shape[1] + 2 * self.num_boundary_points)
216
+ * self.grid_org.dy,
217
+ )
218
+ self.extended_grid = fullwave.Grid(
219
+ domain_size=domain_size,
220
+ f0=self.grid_org.f0,
221
+ duration=self.grid_org.duration,
222
+ c0=self.grid_org.c0,
223
+ ppw=self.grid_org.ppw,
224
+ cfl=self.grid_org.cfl,
225
+ )
226
+
227
+ self.extended_medium = fullwave.Medium(
228
+ grid=self.extended_grid,
229
+ sound_speed=self._extend_map_for_pml(self.medium_org.sound_speed),
230
+ density=self._extend_map_for_pml(self.medium_org.density),
231
+ beta=self._extend_map_for_pml(self.medium_org.beta),
232
+ alpha_coeff=self._extend_map_for_pml(self.medium_org.alpha_coeff),
233
+ alpha_power=self._extend_map_for_pml(self.medium_org.alpha_power),
234
+ air_map=self._extend_map_for_pml(self.medium_org.air_map, fill_edge=False),
235
+ n_relaxation_mechanisms=self.medium_org.n_relaxation_mechanisms,
236
+ path_relaxation_parameters_database=self.medium_org.path_relaxation_parameters_database,
237
+ attenuation_builder=self.medium_org.attenuation_builder,
238
+ )
239
+
240
+ self.extended_source = fullwave.Source(
241
+ p0=self.source_org.p0,
242
+ mask=self._extend_map_for_pml(self.source_org.mask, fill_edge=False),
243
+ )
244
+ self.extended_sensor = fullwave.Sensor(
245
+ mask=self._extend_map_for_pml(self.sensor_org.mask, fill_edge=False),
246
+ sampling_modulus_time=self.sensor_org.sampling_modulus_time,
247
+ )
248
+ if self.is_3d:
249
+ self.pml_mask_x, self.pml_mask_y, self.pml_mask_z = self._localize_pml_region()
250
+ else:
251
+ self.pml_mask_x, self.pml_mask_y = self._localize_pml_region()
252
+
253
+ self.pml_layer_m = self.extended_grid.dx * self.n_pml_layer
254
+ self.transition_layer_m = self.extended_grid.dx * self.n_transition_layer
255
+
256
+ self.n_polynomial = 2
257
+ self.theoritical_reflection_coefficient = 10 ** (-30)
258
+
259
+ if self.n_pml_layer == 0:
260
+ self.n_transition_layer = 0
261
+
262
+ # ---
263
+ @cached_property
264
+ def num_boundary_points(self) -> int:
265
+ """Returns the number of the boundary points.
266
+
267
+ Number of PML layer and ghost cells.
268
+ """
269
+ return self.n_transition_layer + self.n_pml_layer + self.m_spatial_order
270
+
271
+ @cached_property
272
+ def nx(self) -> int:
273
+ """Returns the number of grid points in x-direction."""
274
+ return self.extended_grid.nx
275
+
276
+ @cached_property
277
+ def ny(self) -> int:
278
+ """Returns the number of grid points in y-direction."""
279
+ return self.extended_grid.ny
280
+
281
+ @cached_property
282
+ def nz(self) -> int:
283
+ """Returns the number of grid points in y-direction."""
284
+ return self.extended_grid.nz
285
+
286
+ @cached_property
287
+ def nt(self) -> int:
288
+ """Returns the number of time steps."""
289
+ return self.extended_grid.nt
290
+
291
+ @cached_property
292
+ def n_sources(self) -> int:
293
+ """Return the number of sources."""
294
+ return self.extended_source.n_sources
295
+
296
+ @cached_property
297
+ def n_sensors(self) -> int:
298
+ """Return the number of sources."""
299
+ return self.extended_sensor.n_sensors
300
+
301
+ @cached_property
302
+ def n_air(self) -> int:
303
+ """Return the number of air coordinates."""
304
+ return self.extended_medium.n_air
305
+
306
+ @cached_property
307
+ def n_coords_zero(self) -> int:
308
+ """Return the number of air coordinates.
309
+
310
+ (alias for self.n_air)
311
+ """
312
+ return self.n_air
313
+
314
+ def _extend_map_for_pml( # noqa: PLR0915
315
+ self,
316
+ input_map: NDArray[np.float64 | np.int64 | np.bool],
317
+ *,
318
+ fill_edge: bool = True,
319
+ ) -> NDArray[np.float64 | np.int64 | np.bool]:
320
+ output_map: NDArray[np.float64 | np.int64 | np.bool]
321
+ if self.is_3d:
322
+ output_map = np.zeros(
323
+ (
324
+ input_map.shape[0] + 2 * self.num_boundary_points,
325
+ input_map.shape[1] + 2 * self.num_boundary_points,
326
+ input_map.shape[2] + 2 * self.num_boundary_points,
327
+ ),
328
+ )
329
+ # center
330
+ output_map[
331
+ self.num_boundary_points : -self.num_boundary_points,
332
+ self.num_boundary_points : -self.num_boundary_points,
333
+ self.num_boundary_points : -self.num_boundary_points,
334
+ ] = input_map
335
+ # edges
336
+ if fill_edge:
337
+ output_map[
338
+ self.num_boundary_points : -self.num_boundary_points,
339
+ : self.num_boundary_points,
340
+ self.num_boundary_points : -self.num_boundary_points,
341
+ ] = input_map[:, [0], :]
342
+ output_map[
343
+ self.num_boundary_points : -self.num_boundary_points,
344
+ -self.num_boundary_points :,
345
+ self.num_boundary_points : -self.num_boundary_points,
346
+ ] = input_map[:, [-1], :]
347
+
348
+ output_map[
349
+ : self.num_boundary_points,
350
+ self.num_boundary_points : -self.num_boundary_points,
351
+ self.num_boundary_points : -self.num_boundary_points,
352
+ ] = input_map[[0], :, :]
353
+ output_map[
354
+ -self.num_boundary_points :,
355
+ self.num_boundary_points : -self.num_boundary_points,
356
+ self.num_boundary_points : -self.num_boundary_points,
357
+ ] = input_map[[-1], :, :]
358
+
359
+ output_map[
360
+ self.num_boundary_points : -self.num_boundary_points,
361
+ self.num_boundary_points : -self.num_boundary_points,
362
+ : self.num_boundary_points,
363
+ ] = input_map[:, :, [0]]
364
+ output_map[
365
+ self.num_boundary_points : -self.num_boundary_points,
366
+ self.num_boundary_points : -self.num_boundary_points,
367
+ -self.num_boundary_points :,
368
+ ] = input_map[:, :, [-1]]
369
+
370
+ # corners
371
+ output_map[
372
+ : self.num_boundary_points,
373
+ : self.num_boundary_points,
374
+ : self.num_boundary_points,
375
+ ] = input_map[
376
+ 0,
377
+ 0,
378
+ 0,
379
+ ]
380
+
381
+ output_map[
382
+ -self.num_boundary_points :,
383
+ : self.num_boundary_points,
384
+ : self.num_boundary_points,
385
+ ] = input_map[
386
+ -1,
387
+ 0,
388
+ 0,
389
+ ]
390
+ output_map[
391
+ : self.num_boundary_points,
392
+ -self.num_boundary_points :,
393
+ : self.num_boundary_points,
394
+ ] = input_map[
395
+ 0,
396
+ -1,
397
+ 0,
398
+ ]
399
+ output_map[
400
+ : self.num_boundary_points,
401
+ : self.num_boundary_points,
402
+ -self.num_boundary_points :,
403
+ ] = input_map[
404
+ 0,
405
+ 0,
406
+ -1,
407
+ ]
408
+ output_map[
409
+ -self.num_boundary_points :,
410
+ -self.num_boundary_points :,
411
+ : self.num_boundary_points,
412
+ ] = input_map[
413
+ -1,
414
+ -1,
415
+ 0,
416
+ ]
417
+ output_map[
418
+ : self.num_boundary_points,
419
+ -self.num_boundary_points :,
420
+ -self.num_boundary_points :,
421
+ ] = input_map[
422
+ 0,
423
+ -1,
424
+ -1,
425
+ ]
426
+ output_map[
427
+ -self.num_boundary_points :,
428
+ : self.num_boundary_points,
429
+ -self.num_boundary_points :,
430
+ ] = input_map[
431
+ -1,
432
+ 0,
433
+ -1,
434
+ ]
435
+ output_map[
436
+ -self.num_boundary_points :,
437
+ -self.num_boundary_points :,
438
+ -self.num_boundary_points :,
439
+ ] = input_map[
440
+ -1,
441
+ -1,
442
+ -1,
443
+ ]
444
+ # ---
445
+ output_map[
446
+ : self.num_boundary_points,
447
+ : self.num_boundary_points,
448
+ : self.num_boundary_points,
449
+ ] = input_map[
450
+ 0,
451
+ 0,
452
+ 0,
453
+ ]
454
+ for i in range(input_map.shape[0]):
455
+ output_map[
456
+ self.num_boundary_points + i,
457
+ : self.num_boundary_points,
458
+ : self.num_boundary_points,
459
+ ] = input_map[
460
+ i,
461
+ [0],
462
+ [0],
463
+ ]
464
+ output_map[
465
+ self.num_boundary_points + i,
466
+ : self.num_boundary_points,
467
+ -self.num_boundary_points :,
468
+ ] = input_map[
469
+ i,
470
+ [0],
471
+ [-1],
472
+ ]
473
+ output_map[
474
+ -self.num_boundary_points - (i + 1),
475
+ : self.num_boundary_points,
476
+ : self.num_boundary_points,
477
+ ] = input_map[
478
+ -(i + 1),
479
+ [0],
480
+ [0],
481
+ ]
482
+ output_map[
483
+ -self.num_boundary_points - (i + 1),
484
+ : self.num_boundary_points,
485
+ -self.num_boundary_points :,
486
+ ] = input_map[
487
+ -(i + 1),
488
+ [0],
489
+ [-1],
490
+ ]
491
+ output_map[
492
+ self.num_boundary_points + i,
493
+ -self.num_boundary_points :,
494
+ : self.num_boundary_points,
495
+ ] = input_map[
496
+ i,
497
+ [-1],
498
+ [0],
499
+ ]
500
+
501
+ output_map[
502
+ -self.num_boundary_points - (i + 1),
503
+ -self.num_boundary_points :,
504
+ : self.num_boundary_points,
505
+ ] = input_map[
506
+ -(i + 1),
507
+ [-1],
508
+ [0],
509
+ ]
510
+ output_map[
511
+ -self.num_boundary_points - (i + 1),
512
+ -self.num_boundary_points :,
513
+ -self.num_boundary_points :,
514
+ ] = input_map[
515
+ -(i + 1),
516
+ [-1],
517
+ [-1],
518
+ ]
519
+ for i in range(input_map.shape[1]):
520
+ output_map[
521
+ : self.num_boundary_points,
522
+ self.num_boundary_points + i,
523
+ : self.num_boundary_points,
524
+ ] = input_map[
525
+ [0],
526
+ i,
527
+ [0],
528
+ ]
529
+ output_map[
530
+ : self.num_boundary_points,
531
+ self.num_boundary_points + i,
532
+ -self.num_boundary_points :,
533
+ ] = input_map[
534
+ [0],
535
+ i,
536
+ [-1],
537
+ ]
538
+ output_map[
539
+ : self.num_boundary_points,
540
+ -self.num_boundary_points - (i + 1),
541
+ : self.num_boundary_points,
542
+ ] = input_map[
543
+ [0],
544
+ -(i + 1),
545
+ [0],
546
+ ]
547
+ output_map[
548
+ : self.num_boundary_points,
549
+ -self.num_boundary_points - (i + 1),
550
+ -self.num_boundary_points :,
551
+ ] = input_map[
552
+ [0],
553
+ -(i + 1),
554
+ [-1],
555
+ ]
556
+ output_map[
557
+ -self.num_boundary_points :,
558
+ self.num_boundary_points + i,
559
+ : self.num_boundary_points,
560
+ ] = input_map[
561
+ [-1],
562
+ i,
563
+ [0],
564
+ ]
565
+
566
+ output_map[
567
+ -self.num_boundary_points :,
568
+ -self.num_boundary_points - (i + 1),
569
+ : self.num_boundary_points,
570
+ ] = input_map[
571
+ [-1],
572
+ -(i + 1),
573
+ [0],
574
+ ]
575
+ output_map[
576
+ -self.num_boundary_points :,
577
+ -self.num_boundary_points - (i + 1),
578
+ -self.num_boundary_points :,
579
+ ] = input_map[
580
+ [-1],
581
+ -(i + 1),
582
+ [-1],
583
+ ]
584
+ for i in range(input_map.shape[2]):
585
+ output_map[
586
+ : self.num_boundary_points,
587
+ : self.num_boundary_points,
588
+ self.num_boundary_points + i,
589
+ ] = input_map[
590
+ [0],
591
+ [0],
592
+ i,
593
+ ]
594
+ output_map[
595
+ : self.num_boundary_points,
596
+ -self.num_boundary_points :,
597
+ self.num_boundary_points + i,
598
+ ] = input_map[
599
+ [0],
600
+ [-1],
601
+ i,
602
+ ]
603
+ output_map[
604
+ : self.num_boundary_points,
605
+ : self.num_boundary_points,
606
+ -self.num_boundary_points - (i + 1),
607
+ ] = input_map[
608
+ [0],
609
+ [0],
610
+ -(i + 1),
611
+ ]
612
+ output_map[
613
+ : self.num_boundary_points,
614
+ -self.num_boundary_points :,
615
+ -self.num_boundary_points - (i + 1),
616
+ ] = input_map[
617
+ [0],
618
+ [-1],
619
+ -(i + 1),
620
+ ]
621
+ output_map[
622
+ -self.num_boundary_points :,
623
+ : self.num_boundary_points,
624
+ self.num_boundary_points + i,
625
+ ] = input_map[
626
+ [-1],
627
+ [0],
628
+ i,
629
+ ]
630
+
631
+ output_map[
632
+ -self.num_boundary_points :,
633
+ : self.num_boundary_points,
634
+ -self.num_boundary_points - (i + 1),
635
+ ] = input_map[
636
+ [-1],
637
+ [0],
638
+ -(i + 1),
639
+ ]
640
+ output_map[
641
+ -self.num_boundary_points :,
642
+ -self.num_boundary_points :,
643
+ -self.num_boundary_points - (i + 1),
644
+ ] = input_map[
645
+ [-1],
646
+ [-1],
647
+ -(i + 1),
648
+ ]
649
+ else:
650
+ output_map = np.zeros(
651
+ (
652
+ input_map.shape[0] + 2 * self.num_boundary_points,
653
+ input_map.shape[1] + 2 * self.num_boundary_points,
654
+ ),
655
+ )
656
+ # center
657
+ output_map[
658
+ self.num_boundary_points : -self.num_boundary_points,
659
+ self.num_boundary_points : -self.num_boundary_points,
660
+ ] = input_map
661
+
662
+ # edges
663
+ if fill_edge:
664
+ output_map[
665
+ self.num_boundary_points : -self.num_boundary_points,
666
+ : self.num_boundary_points,
667
+ ] = input_map[:, [0]]
668
+ output_map[
669
+ self.num_boundary_points : -self.num_boundary_points,
670
+ -self.num_boundary_points :,
671
+ ] = input_map[:, [-1]]
672
+
673
+ output_map[
674
+ : self.num_boundary_points,
675
+ self.num_boundary_points : -self.num_boundary_points,
676
+ ] = input_map[[0], :]
677
+ output_map[
678
+ -self.num_boundary_points :,
679
+ self.num_boundary_points : -self.num_boundary_points,
680
+ ] = input_map[[-1], :]
681
+
682
+ # corners
683
+ output_map[: self.num_boundary_points, : self.num_boundary_points] = input_map[0, 0]
684
+ output_map[-self.num_boundary_points :, -self.num_boundary_points :] = input_map[
685
+ -1,
686
+ -1,
687
+ ]
688
+ output_map[-self.num_boundary_points :, : self.num_boundary_points] = input_map[
689
+ -1,
690
+ 0,
691
+ ]
692
+ output_map[: self.num_boundary_points, -self.num_boundary_points :] = input_map[
693
+ 0,
694
+ -1,
695
+ ]
696
+
697
+ return output_map
698
+
699
+ def _extend_relaxation_param_dict(
700
+ self,
701
+ relaxation_param_dict: dict[str, NDArray[np.float64 | np.int64 | np.bool]],
702
+ ) -> dict[str, NDArray[np.float64 | np.int64 | np.bool]]:
703
+ output_dict = {}
704
+ for key, value in relaxation_param_dict.items():
705
+ output_dict[key] = self._extend_map_for_pml(value)
706
+ return output_dict
707
+
708
+ def _localize_pml_region(self) -> tuple[NDArray[np.float64], ...]:
709
+ pml_mask_x: NDArray[np.float64]
710
+ pml_mask_y: NDArray[np.float64]
711
+ pml_mask_z: NDArray[np.float64]
712
+ if self.is_3d:
713
+ n_x_extended, n_y_extended, n_z_extended = self.extended_medium.sound_speed.shape
714
+
715
+ pml_mask_x = np.zeros((n_x_extended, n_y_extended, n_z_extended))
716
+ pml_mask_y = np.zeros((n_x_extended, n_y_extended, n_z_extended))
717
+ pml_mask_z = np.zeros((n_x_extended, n_y_extended, n_z_extended))
718
+ for i in range(self.n_pml_layer):
719
+ pml_mask_x[
720
+ i + (n_x_extended - self.m_spatial_order - self.n_pml_layer),
721
+ :,
722
+ :,
723
+ ] = i / self.n_pml_layer
724
+
725
+ pml_mask_x[self.m_spatial_order + self.n_pml_layer - i - 1, :, :] = (
726
+ i / self.n_pml_layer
727
+ )
728
+
729
+ pml_mask_y[
730
+ :,
731
+ i + (n_y_extended - self.m_spatial_order - self.n_pml_layer),
732
+ :,
733
+ ] = i / self.n_pml_layer
734
+
735
+ pml_mask_y[:, self.m_spatial_order + self.n_pml_layer - i - 1, :] = (
736
+ i / self.n_pml_layer
737
+ )
738
+
739
+ pml_mask_z[
740
+ :,
741
+ :,
742
+ i + (n_z_extended - self.m_spatial_order - self.n_pml_layer),
743
+ ] = i / self.n_pml_layer
744
+
745
+ pml_mask_z[:, :, self.m_spatial_order + self.n_pml_layer - i - 1] = (
746
+ i / self.n_pml_layer
747
+ )
748
+
749
+ pml_mask_x[0 : self.m_spatial_order, :, :] = 1
750
+ pml_mask_x[n_x_extended - self.m_spatial_order : n_x_extended, :, :] = 1
751
+
752
+ pml_mask_y[:, 0 : self.m_spatial_order, :] = 1
753
+ pml_mask_y[:, n_y_extended - self.m_spatial_order : n_y_extended, :] = 1
754
+
755
+ pml_mask_z[:, :, 0 : self.m_spatial_order] = 1
756
+ pml_mask_z[:, :, n_z_extended - self.m_spatial_order : n_z_extended] = 1
757
+ return pml_mask_x, pml_mask_y, pml_mask_z
758
+
759
+ n_x_extended, n_y_extended = self.extended_medium.sound_speed.shape
760
+
761
+ pml_mask_x = np.zeros((n_x_extended, n_y_extended))
762
+ pml_mask_y = np.zeros((n_x_extended, n_y_extended))
763
+
764
+ for i in range(self.n_pml_layer):
765
+ pml_mask_x[
766
+ i + (n_x_extended - self.m_spatial_order - self.n_pml_layer),
767
+ :,
768
+ ] = i / self.n_pml_layer
769
+
770
+ pml_mask_x[self.m_spatial_order + self.n_pml_layer - i - 1, :] = i / self.n_pml_layer
771
+
772
+ pml_mask_y[
773
+ :,
774
+ i + (n_y_extended - self.m_spatial_order - self.n_pml_layer),
775
+ ] = i / self.n_pml_layer
776
+
777
+ pml_mask_y[:, self.m_spatial_order + self.n_pml_layer - i - 1] = i / self.n_pml_layer
778
+
779
+ pml_mask_x[0 : self.m_spatial_order, :] = 1
780
+ pml_mask_x[n_x_extended - self.m_spatial_order : n_x_extended, :] = 1
781
+
782
+ pml_mask_y[:, 0 : self.m_spatial_order] = 1
783
+ pml_mask_y[:, n_y_extended - self.m_spatial_order : n_y_extended] = 1
784
+
785
+ return pml_mask_x, pml_mask_y
786
+
787
+ @staticmethod
788
+ def _calc_a_and_b(
789
+ d_x: NDArray[np.float64] | float,
790
+ kappa_x: NDArray[np.float64] | float,
791
+ alpha_x: NDArray[np.float64] | float,
792
+ dt: NDArray[np.float64] | float,
793
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
794
+ # function [a b] = ab(dx,kappax,alphax,dT)
795
+ d_x = np.array(d_x)
796
+ kappa_x = np.array(kappa_x)
797
+ alpha_x = np.array(alpha_x)
798
+ dt = np.array(dt)
799
+
800
+ b = np.exp(-(d_x / kappa_x + alpha_x) * dt)
801
+ eps = 1e-10
802
+ a = d_x / (kappa_x * (d_x + kappa_x * alpha_x) + eps) * (b - 1)
803
+ return a, b
804
+
805
+ def run(self, *, use_pml: bool = True) -> fullwave.MediumRelaxationMaps:
806
+ """Generate perfect matched layer (PML) relaxation parameters.
807
+
808
+ It generates the relaxation parameters
809
+ for the PML region considering the given medium and PML parameters.
810
+
811
+ Returns
812
+ -------
813
+ Medium
814
+ A Medium instance with the constructed domain properties.
815
+
816
+ """
817
+ if use_pml:
818
+ extended_medium: fullwave.MediumRelaxationMaps = self.extended_medium.build()
819
+ if self.is_3d:
820
+ return self._apply_pml_3d(
821
+ extended_medium=extended_medium,
822
+ theoritical_reflection_coefficient=self.theoritical_reflection_coefficient,
823
+ n_polynomial=self.n_polynomial,
824
+ )
825
+
826
+ return self._apply_pml(
827
+ extended_medium=extended_medium,
828
+ theoritical_reflection_coefficient=self.theoritical_reflection_coefficient,
829
+ n_polynomial=self.n_polynomial,
830
+ )
831
+
832
+ extended_medium: fullwave.MediumRelaxationMaps = self.extended_medium.build()
833
+ return extended_medium
834
+
835
+ def _apply_pml(
836
+ self,
837
+ extended_medium: fullwave.MediumRelaxationMaps,
838
+ theoritical_reflection_coefficient: float,
839
+ n_polynomial: float,
840
+ ) -> fullwave.MediumRelaxationMaps:
841
+ """Apply PML to the extended medium relaxation parameters.
842
+
843
+ ref: Komatitsch, D., & Martin, R. (2007).
844
+ An unsplit convolutional perfectly matched layer improved
845
+ at grazing incidence for the seismic wave equation.
846
+ Geophysics, 72(5), SM155-SM167. https://doi.org/10.1190/1.2757586
847
+
848
+ Parameters
849
+ ----------
850
+ extended_medium : fullwave.MediumRelaxationMaps
851
+ The extended medium relaxation parameters.
852
+ n_polynomial : float
853
+ The polynomial order for the PML damping parameter.
854
+ it changes the transition function shape from the medium to the PML.
855
+ theoritical_reflection_coefficient : float
856
+ The theoretical reflection coefficient for the PML.
857
+ it changes the PML strength. it gets unstable if it is too low.
858
+
859
+ Returns
860
+ -------
861
+ fullwave.MediumRelaxationMaps
862
+ The extended medium relaxation parameters with PML applied.
863
+
864
+ """
865
+ # alpha=0 and d=0 will make a and b in the PML be 0
866
+ # this procedure shrinks the multiple relaxation mechanisms to a single one
867
+ alpha_target_pml = 0
868
+ alpha_target_higher_nu = 0
869
+ d_target_higher_nu = 0
870
+
871
+ # see Komatitsch, D., & Martin, R. (2007), SM160
872
+ d_target_pml = (
873
+ -(n_polynomial + 1)
874
+ * self.extended_grid.c0
875
+ * np.log(theoritical_reflection_coefficient)
876
+ / (2 * (self.pml_layer_m + self.transition_layer_m))
877
+ # / (2 * (self.pml_layer_m))
878
+ )
879
+ # alpha_pml_entrance = np.pi * self.extended_grid.f0
880
+
881
+ out_dict = {}
882
+ relaxation_param_dict = extended_medium.relaxation_param_dict
883
+ rename_dict = _obtain_relax_var_rename_dict(
884
+ n_relaxation_mechanisms=self.extended_medium.n_relaxation_mechanisms,
885
+ is_3d=self.is_3d,
886
+ use_isotropic_relaxation=self.use_isotropic_relaxation,
887
+ )
888
+ for key_fw2, key_py in rename_dict.items():
889
+ if key_fw2 in ["kappa_x", "kappa_u", "kappa_y", "kappa_w"]:
890
+ out_dict[key_fw2] = relaxation_param_dict[key_py].copy()
891
+ elif (
892
+ ("alpha_u_nu" in key_fw2 and "nu1" not in key_fw2)
893
+ or ("alpha_x_nu" in key_fw2 and "nu1" not in key_fw2)
894
+ or ("alpha_w_nu" in key_fw2 and "nu1" not in key_fw2)
895
+ or ("alpha_y_nu" in key_fw2 and "nu1" not in key_fw2)
896
+ ):
897
+ # out_dict[key_fw2] = relaxation_param_dict[key_py].copy()
898
+ out_dict[key_fw2] = self._apply_transition_and_pml(
899
+ relaxation_param_dict[key_py].copy(),
900
+ value_target=alpha_target_higher_nu,
901
+ array_shape=relaxation_param_dict[key_py].shape,
902
+ axis=0,
903
+ transition_type="cosine",
904
+ transit_within_transition_layer=True,
905
+ is_3d=self.is_3d,
906
+ )
907
+ out_dict[key_fw2] = self._apply_transition_and_pml(
908
+ out_dict[key_fw2],
909
+ # relaxation_param_dict[key_py].copy(),
910
+ value_target=alpha_target_higher_nu,
911
+ array_shape=relaxation_param_dict[key_py].shape,
912
+ axis=1,
913
+ transition_type="cosine",
914
+ transit_within_transition_layer=True,
915
+ is_3d=self.is_3d,
916
+ )
917
+ elif (
918
+ ("d_u_nu" in key_fw2 and "nu1" not in key_fw2)
919
+ or ("d_x_nu" in key_fw2 and "nu1" not in key_fw2)
920
+ or ("d_w_nu" in key_fw2 and "nu1" not in key_fw2)
921
+ or ("d_y_nu" in key_fw2 and "nu1" not in key_fw2)
922
+ ):
923
+ # out_dict[key_fw2] = relaxation_param_dict[key_py].copy()
924
+ out_dict[key_fw2] = self._apply_transition_and_pml(
925
+ relaxation_param_dict[key_py].copy(),
926
+ value_target=d_target_higher_nu,
927
+ array_shape=relaxation_param_dict[key_py].shape,
928
+ axis=0,
929
+ transition_type="cosine",
930
+ transit_within_transition_layer=True,
931
+ is_3d=self.is_3d,
932
+ )
933
+ out_dict[key_fw2] = self._apply_transition_and_pml(
934
+ out_dict[key_fw2],
935
+ # relaxation_param_dict[key_py].copy(),
936
+ value_target=d_target_higher_nu,
937
+ array_shape=relaxation_param_dict[key_py].shape,
938
+ axis=1,
939
+ transition_type="cosine",
940
+ transit_within_transition_layer=True,
941
+ is_3d=self.is_3d,
942
+ )
943
+ elif (
944
+ ("alpha_u_nu" in key_fw2 and "nu1" in key_fw2)
945
+ or ("alpha_x_nu" in key_fw2 and "nu1" in key_fw2)
946
+ or ("alpha_w_nu" in key_fw2 and "nu1" in key_fw2)
947
+ or ("alpha_y_nu" in key_fw2 and "nu1" in key_fw2)
948
+ ):
949
+ # out_dict[key_fw2] = relaxation_param_dict[key_py].copy()
950
+ out_dict[key_fw2] = self._apply_transition_and_pml(
951
+ relaxation_param_dict[key_py].copy(),
952
+ value_target=alpha_target_pml,
953
+ array_shape=relaxation_param_dict[key_py].shape,
954
+ axis=0,
955
+ transition_type="linear",
956
+ transit_within_transition_layer=False,
957
+ transit_within_pml_layer=False,
958
+ is_3d=self.is_3d,
959
+ )
960
+ out_dict[key_fw2] = self._apply_transition_and_pml(
961
+ out_dict[key_fw2],
962
+ # relaxation_param_dict[key_py].copy(),
963
+ value_target=alpha_target_pml,
964
+ array_shape=relaxation_param_dict[key_py].shape,
965
+ axis=1,
966
+ transition_type="linear",
967
+ transit_within_transition_layer=False,
968
+ transit_within_pml_layer=False,
969
+ is_3d=self.is_3d,
970
+ )
971
+ elif (
972
+ ("d_u_nu" in key_fw2 and "nu1" in key_fw2)
973
+ or ("d_x_nu" in key_fw2 and "nu1" in key_fw2)
974
+ or ("d_w_nu" in key_fw2 and "nu1" in key_fw2)
975
+ or ("d_y_nu" in key_fw2 and "nu1" in key_fw2)
976
+ ):
977
+ out_dict[key_fw2] = self._apply_transition_and_pml(
978
+ relaxation_param_dict[key_py].copy(),
979
+ value_target=d_target_pml,
980
+ array_shape=relaxation_param_dict[key_py].shape,
981
+ axis=0,
982
+ n_polynomial=n_polynomial,
983
+ transition_type="polynomial",
984
+ transit_within_transition_layer=False,
985
+ transit_within_pml_layer=False,
986
+ is_3d=self.is_3d,
987
+ )
988
+ out_dict[key_fw2] = self._apply_transition_and_pml(
989
+ out_dict[key_fw2],
990
+ # relaxation_param_dict[key_py].copy(),
991
+ value_target=d_target_pml,
992
+ array_shape=relaxation_param_dict[key_py].shape,
993
+ axis=1,
994
+ n_polynomial=n_polynomial,
995
+ transition_type="polynomial",
996
+ transit_within_transition_layer=False,
997
+ transit_within_pml_layer=False,
998
+ is_3d=self.is_3d,
999
+ )
1000
+
1001
+ axis_list = ["u", "x"] if self.use_isotropic_relaxation else ["u", "w", "x", "y"]
1002
+ for nu in range(1, extended_medium.n_relaxation_mechanisms + 1):
1003
+ for axis in axis_list:
1004
+ (
1005
+ out_dict[f"a_pml_{axis}{nu}"],
1006
+ out_dict[f"b_pml_{axis}{nu}"],
1007
+ ) = self._calc_a_and_b(
1008
+ d_x=out_dict[f"d_{axis}_nu{nu}"],
1009
+ kappa_x=out_dict[f"kappa_{axis}"],
1010
+ alpha_x=out_dict[f"alpha_{axis}_nu{nu}"],
1011
+ dt=extended_medium.grid.dt,
1012
+ )
1013
+
1014
+ extended_medium.relaxation_param_dict_for_fw2.update(
1015
+ out_dict,
1016
+ )
1017
+
1018
+ return extended_medium
1019
+
1020
+ def _apply_pml_3d(
1021
+ self,
1022
+ extended_medium: fullwave.MediumRelaxationMaps,
1023
+ theoritical_reflection_coefficient: float,
1024
+ n_polynomial: float,
1025
+ ) -> fullwave.MediumRelaxationMaps:
1026
+ """Apply PML to the extended medium relaxation parameters.
1027
+
1028
+ ref: Komatitsch, D., & Martin, R. (2007).
1029
+ An unsplit convolutional perfectly matched layer improved
1030
+ at grazing incidence for the seismic wave equation.
1031
+ Geophysics, 72(5), SM155-SM167. https://doi.org/10.1190/1.2757586
1032
+
1033
+ Parameters
1034
+ ----------
1035
+ extended_medium : fullwave.MediumRelaxationMaps
1036
+ The extended medium relaxation parameters.
1037
+ n_polynomial : float
1038
+ The polynomial order for the PML damping parameter.
1039
+ it changes the transition function shape from the medium to the PML.
1040
+ theoritical_reflection_coefficient : float
1041
+ The theoretical reflection coefficient for the PML.
1042
+ it changes the PML strength. it gets unstable if it is too low.
1043
+
1044
+ Returns
1045
+ -------
1046
+ fullwave.MediumRelaxationMaps
1047
+ The extended medium relaxation parameters with PML applied.
1048
+
1049
+ """
1050
+ # alpha=0 and d=0 will make a and b in the PML be 0
1051
+ # this procedure shrinks the multiple relaxation mechanisms to a single one
1052
+ alpha_target_pml = 0
1053
+ alpha_target_higher_nu = 0
1054
+ d_target_higher_nu = 0
1055
+
1056
+ # see Komatitsch, D., & Martin, R. (2007), SM160
1057
+ d_target_pml = (
1058
+ -(n_polynomial + 1)
1059
+ * self.extended_grid.c0
1060
+ * np.log(theoritical_reflection_coefficient)
1061
+ / (2 * (self.pml_layer_m + self.transition_layer_m))
1062
+ # / (2 * self.pml_layer_m)
1063
+ )
1064
+
1065
+ out_dict = {}
1066
+ relaxation_param_dict = extended_medium.relaxation_param_dict
1067
+ rename_dict = _obtain_relax_var_rename_dict(
1068
+ n_relaxation_mechanisms=self.extended_medium.n_relaxation_mechanisms,
1069
+ is_3d=self.is_3d,
1070
+ use_isotropic_relaxation=self.use_isotropic_relaxation,
1071
+ )
1072
+ for key_fw2, key_py in rename_dict.items():
1073
+ if (
1074
+ key_fw2 in ["kappa_x", "kappa_u"]
1075
+ or key_fw2 in ["kappa_y", "kappa_v"]
1076
+ or key_fw2 in ["kappa_z", "kappa_w"]
1077
+ ):
1078
+ out_dict[key_fw2] = relaxation_param_dict[key_py].copy()
1079
+ elif (
1080
+ ("alpha_u_nu" in key_fw2 and "nu1" not in key_fw2)
1081
+ or ("alpha_v_nu" in key_fw2 and "nu1" not in key_fw2)
1082
+ or ("alpha_w_nu" in key_fw2 and "nu1" not in key_fw2)
1083
+ or ("alpha_x_nu" in key_fw2 and "nu1" not in key_fw2)
1084
+ or ("alpha_y_nu" in key_fw2 and "nu1" not in key_fw2)
1085
+ or ("alpha_z_nu" in key_fw2 and "nu1" not in key_fw2)
1086
+ ):
1087
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1088
+ relaxation_param_dict[key_py].copy(),
1089
+ value_target=alpha_target_higher_nu,
1090
+ array_shape=relaxation_param_dict[key_py].shape,
1091
+ axis=0,
1092
+ transition_type="cosine",
1093
+ transit_within_transition_layer=True,
1094
+ is_3d=self.is_3d,
1095
+ )
1096
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1097
+ out_dict[key_fw2],
1098
+ value_target=alpha_target_higher_nu,
1099
+ array_shape=relaxation_param_dict[key_py].shape,
1100
+ axis=1,
1101
+ transition_type="cosine",
1102
+ transit_within_transition_layer=True,
1103
+ is_3d=self.is_3d,
1104
+ )
1105
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1106
+ out_dict[key_fw2],
1107
+ value_target=alpha_target_higher_nu,
1108
+ array_shape=relaxation_param_dict[key_py].shape,
1109
+ axis=2,
1110
+ transition_type="cosine",
1111
+ transit_within_transition_layer=True,
1112
+ is_3d=self.is_3d,
1113
+ )
1114
+ elif (
1115
+ ("d_u_nu" in key_fw2 and "nu1" not in key_fw2)
1116
+ or ("d_v_nu" in key_fw2 and "nu1" not in key_fw2)
1117
+ or ("d_w_nu" in key_fw2 and "nu1" not in key_fw2)
1118
+ or ("d_x_nu" in key_fw2 and "nu1" not in key_fw2)
1119
+ or ("d_y_nu" in key_fw2 and "nu1" not in key_fw2)
1120
+ or ("d_z_nu" in key_fw2 and "nu1" not in key_fw2)
1121
+ ):
1122
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1123
+ relaxation_param_dict[key_py].copy(),
1124
+ value_target=d_target_higher_nu,
1125
+ array_shape=relaxation_param_dict[key_py].shape,
1126
+ axis=0,
1127
+ transition_type="cosine",
1128
+ transit_within_transition_layer=True,
1129
+ is_3d=self.is_3d,
1130
+ )
1131
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1132
+ out_dict[key_fw2],
1133
+ value_target=d_target_higher_nu,
1134
+ array_shape=relaxation_param_dict[key_py].shape,
1135
+ axis=1,
1136
+ transition_type="cosine",
1137
+ transit_within_transition_layer=True,
1138
+ is_3d=self.is_3d,
1139
+ )
1140
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1141
+ out_dict[key_fw2],
1142
+ value_target=d_target_higher_nu,
1143
+ array_shape=relaxation_param_dict[key_py].shape,
1144
+ axis=2,
1145
+ transition_type="cosine",
1146
+ transit_within_transition_layer=True,
1147
+ is_3d=self.is_3d,
1148
+ )
1149
+ elif (
1150
+ ("alpha_u_nu" in key_fw2 and "nu1" in key_fw2)
1151
+ or ("alpha_v_nu" in key_fw2 and "nu1" in key_fw2)
1152
+ or ("alpha_w_nu" in key_fw2 and "nu1" in key_fw2)
1153
+ or ("alpha_x_nu" in key_fw2 and "nu1" in key_fw2)
1154
+ or ("alpha_y_nu" in key_fw2 and "nu1" in key_fw2)
1155
+ or ("alpha_z_nu" in key_fw2 and "nu1" in key_fw2)
1156
+ ):
1157
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1158
+ relaxation_param_dict[key_py].copy(),
1159
+ value_target=alpha_target_pml,
1160
+ array_shape=relaxation_param_dict[key_py].shape,
1161
+ axis=0,
1162
+ transition_type="linear",
1163
+ transit_within_transition_layer=False,
1164
+ transit_within_pml_layer=False,
1165
+ is_3d=self.is_3d,
1166
+ )
1167
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1168
+ out_dict[key_fw2],
1169
+ value_target=alpha_target_pml,
1170
+ array_shape=relaxation_param_dict[key_py].shape,
1171
+ axis=1,
1172
+ transition_type="linear",
1173
+ transit_within_transition_layer=False,
1174
+ transit_within_pml_layer=False,
1175
+ is_3d=self.is_3d,
1176
+ )
1177
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1178
+ out_dict[key_fw2],
1179
+ value_target=alpha_target_pml,
1180
+ array_shape=relaxation_param_dict[key_py].shape,
1181
+ axis=2,
1182
+ transition_type="linear",
1183
+ transit_within_transition_layer=False,
1184
+ transit_within_pml_layer=False,
1185
+ is_3d=self.is_3d,
1186
+ )
1187
+ elif (
1188
+ ("d_u_nu" in key_fw2 and "nu1" in key_fw2)
1189
+ or ("d_v_nu" in key_fw2 and "nu1" in key_fw2)
1190
+ or ("d_w_nu" in key_fw2 and "nu1" in key_fw2)
1191
+ or ("d_x_nu" in key_fw2 and "nu1" in key_fw2)
1192
+ or ("d_y_nu" in key_fw2 and "nu1" in key_fw2)
1193
+ or ("d_z_nu" in key_fw2 and "nu1" in key_fw2)
1194
+ ):
1195
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1196
+ relaxation_param_dict[key_py].copy(),
1197
+ value_target=d_target_pml,
1198
+ array_shape=relaxation_param_dict[key_py].shape,
1199
+ axis=0,
1200
+ n_polynomial=n_polynomial,
1201
+ transition_type="polynomial",
1202
+ transit_within_transition_layer=False,
1203
+ transit_within_pml_layer=False,
1204
+ is_3d=self.is_3d,
1205
+ )
1206
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1207
+ out_dict[key_fw2],
1208
+ value_target=d_target_pml,
1209
+ array_shape=relaxation_param_dict[key_py].shape,
1210
+ axis=1,
1211
+ n_polynomial=n_polynomial,
1212
+ transition_type="polynomial",
1213
+ transit_within_transition_layer=False,
1214
+ transit_within_pml_layer=False,
1215
+ is_3d=self.is_3d,
1216
+ )
1217
+ out_dict[key_fw2] = self._apply_transition_and_pml(
1218
+ out_dict[key_fw2],
1219
+ value_target=d_target_pml,
1220
+ array_shape=relaxation_param_dict[key_py].shape,
1221
+ axis=2,
1222
+ n_polynomial=n_polynomial,
1223
+ transition_type="polynomial",
1224
+ transit_within_transition_layer=False,
1225
+ transit_within_pml_layer=False,
1226
+ is_3d=self.is_3d,
1227
+ )
1228
+
1229
+ axis_list = ["u", "x"] if self.use_isotropic_relaxation else ["u", "v", "w", "x", "y", "z"]
1230
+
1231
+ for nu in range(1, extended_medium.n_relaxation_mechanisms + 1):
1232
+ for axis in axis_list:
1233
+ (
1234
+ out_dict[f"a_pml_{axis}{nu}"],
1235
+ out_dict[f"b_pml_{axis}{nu}"],
1236
+ ) = self._calc_a_and_b(
1237
+ d_x=out_dict[f"d_{axis}_nu{nu}"],
1238
+ kappa_x=out_dict[f"kappa_{axis}"],
1239
+ alpha_x=out_dict[f"alpha_{axis}_nu{nu}"],
1240
+ dt=extended_medium.grid.dt,
1241
+ )
1242
+
1243
+ extended_medium.relaxation_param_dict_for_fw2.update(
1244
+ out_dict,
1245
+ )
1246
+
1247
+ return extended_medium
1248
+
1249
+ def _apply_transition_and_pml( # noqa: PLR0912 C901, PLR0915
1250
+ self,
1251
+ input_array: NDArray[np.float64],
1252
+ value_target: float,
1253
+ array_shape: tuple[int, ...],
1254
+ axis: int = 0,
1255
+ *,
1256
+ transition_type: str = "smooth",
1257
+ n_polynomial: float = 2,
1258
+ transit_within_transition_layer: bool = False,
1259
+ transit_within_pml_layer: bool = False,
1260
+ disable_the_transition_and_pml: bool = False,
1261
+ is_3d: bool = False,
1262
+ ) -> NDArray[np.float64]:
1263
+ if transit_within_transition_layer and transit_within_pml_layer:
1264
+ error_msg = (
1265
+ "Both transit_within_transition_layer and transit_within_pml_layer "
1266
+ "cannot be True at the same time."
1267
+ )
1268
+ raise ValueError(error_msg)
1269
+
1270
+ if disable_the_transition_and_pml:
1271
+ return input_array
1272
+
1273
+ if transit_within_transition_layer and self.n_transition_layer == 0:
1274
+ error_msg = (
1275
+ "Transition layer is not defined. "
1276
+ "Set transit_within_transition_layer to False or define n_transition_layer."
1277
+ )
1278
+ raise ValueError(error_msg)
1279
+
1280
+ if transit_within_transition_layer:
1281
+ layer_thickness = self.n_transition_layer
1282
+ layer_offset = self.n_pml_layer
1283
+ elif transit_within_pml_layer:
1284
+ layer_thickness = self.n_pml_layer
1285
+ layer_offset = 0
1286
+ else:
1287
+ layer_thickness = self.n_pml_layer + self.n_transition_layer
1288
+ layer_offset = 0
1289
+
1290
+ if transition_type == "smooth":
1291
+ transition_function = _smooth_transition_function(
1292
+ np.linspace(
1293
+ 0,
1294
+ 1,
1295
+ layer_thickness + 1,
1296
+ ),
1297
+ )
1298
+ elif transition_type == "linear":
1299
+ transition_function = _linear_transition_function(
1300
+ np.linspace(
1301
+ 0,
1302
+ 1,
1303
+ layer_thickness + 1,
1304
+ ),
1305
+ )
1306
+ elif transition_type == "polynomial":
1307
+ transition_function = _n_th_deg_polynomial_function(
1308
+ np.linspace(
1309
+ 0,
1310
+ 1,
1311
+ layer_thickness + 1,
1312
+ ),
1313
+ n=n_polynomial,
1314
+ )
1315
+ elif transition_type == "cosine":
1316
+ transition_function = _cosine_transition_function(
1317
+ np.linspace(
1318
+ 0,
1319
+ 1,
1320
+ layer_thickness + 1,
1321
+ ),
1322
+ )
1323
+ else:
1324
+ error_msg = (
1325
+ f"Invalid transition type: {transition_type}. "
1326
+ "Choose from 'smooth', 'linear', or 'polynomial'."
1327
+ )
1328
+ raise ValueError(error_msg)
1329
+
1330
+ n_axis_extended = array_shape[axis]
1331
+
1332
+ if axis == 0:
1333
+ input_array[: self.m_spatial_order + layer_offset + layer_thickness] = value_target
1334
+ input_array[
1335
+ n_axis_extended - self.m_spatial_order - layer_thickness - layer_offset :,
1336
+ ] = value_target
1337
+ if is_3d:
1338
+ up_start = self.m_spatial_order + layer_offset - 1
1339
+ up_end = self.m_spatial_order + layer_offset + layer_thickness
1340
+ up_slice = slice(up_start, up_end)
1341
+
1342
+ down_start = (
1343
+ n_axis_extended - self.m_spatial_order - layer_thickness - layer_offset - 1
1344
+ )
1345
+ down_end = n_axis_extended - self.m_spatial_order - layer_offset
1346
+ down_slice = slice(down_start, down_end)
1347
+
1348
+ # fetch the "mid" and "down" face values and expand dims for broadcasting
1349
+ up_vals = input_array[up_end, :, :][None, :, :]
1350
+ down_vals = input_array[down_start, :, :][None, :, :]
1351
+
1352
+ # top transition (use reversed transition function)
1353
+ input_array[up_slice, :, :] = up_vals - transition_function[::-1][
1354
+ :,
1355
+ None,
1356
+ None,
1357
+ ] * (up_vals - value_target)
1358
+
1359
+ # bottom transition (forward transition function)
1360
+ input_array[down_slice, :, :] = down_vals - transition_function[:, None, None] * (
1361
+ down_vals - value_target
1362
+ )
1363
+ else:
1364
+ up_start = self.m_spatial_order + layer_offset - 1
1365
+ up_end = self.m_spatial_order + layer_offset + layer_thickness
1366
+ up_slice = slice(up_start, up_end)
1367
+
1368
+ down_start = (
1369
+ n_axis_extended - self.m_spatial_order - layer_thickness - layer_offset - 1
1370
+ )
1371
+ down_end = n_axis_extended - self.m_spatial_order - layer_offset
1372
+ down_slice = slice(down_start, down_end)
1373
+
1374
+ # fetch the "mid" and "down" face values and expand dims for broadcasting
1375
+ up_vals = input_array[up_end, :][None, :]
1376
+ down_vals = input_array[down_start, :][None, :]
1377
+
1378
+ # top transition (use reversed transition function)
1379
+ input_array[up_slice, :] = up_vals - transition_function[::-1][:, None] * (
1380
+ up_vals - value_target
1381
+ )
1382
+
1383
+ # bottom transition (forward transition function)
1384
+ input_array[down_slice, :] = down_vals - transition_function[:, None] * (
1385
+ down_vals - value_target
1386
+ )
1387
+ elif axis == 1:
1388
+ input_array[:, : self.m_spatial_order + layer_offset + layer_thickness] = value_target
1389
+ input_array[
1390
+ :,
1391
+ n_axis_extended - self.m_spatial_order - layer_thickness - layer_offset :,
1392
+ ] = value_target
1393
+ if is_3d:
1394
+ up_start = self.m_spatial_order + layer_offset - 1
1395
+ up_end = self.m_spatial_order + layer_offset + layer_thickness
1396
+ up_slice = slice(up_start, up_end)
1397
+
1398
+ down_start = (
1399
+ n_axis_extended - self.m_spatial_order - layer_thickness - layer_offset - 1
1400
+ )
1401
+ down_end = n_axis_extended - self.m_spatial_order - layer_offset
1402
+ down_slice = slice(down_start, down_end)
1403
+
1404
+ # fetch the "mid" and "down" face values and expand dims for broadcasting
1405
+ up_vals = input_array[:, up_end, :][:, None, :]
1406
+ down_vals = input_array[:, down_start, :][:, None, :]
1407
+
1408
+ # top transition (use reversed transition function)
1409
+ input_array[:, up_slice, :] = up_vals - transition_function[::-1][
1410
+ None,
1411
+ :,
1412
+ None,
1413
+ ] * (up_vals - value_target)
1414
+
1415
+ # bottom transition (forward transition function)
1416
+ input_array[:, down_slice, :] = down_vals - transition_function[None, :, None] * (
1417
+ down_vals - value_target
1418
+ )
1419
+ else:
1420
+ up_start = self.m_spatial_order + layer_offset - 1
1421
+ up_end = self.m_spatial_order + layer_offset + layer_thickness
1422
+ up_slice = slice(up_start, up_end)
1423
+
1424
+ down_start = (
1425
+ n_axis_extended - self.m_spatial_order - layer_thickness - layer_offset - 1
1426
+ )
1427
+ down_end = n_axis_extended - self.m_spatial_order - layer_offset
1428
+ down_slice = slice(down_start, down_end)
1429
+
1430
+ # fetch the "mid" and "down" face values and expand dims for broadcasting
1431
+ up_vals = input_array[:, up_end][:, None]
1432
+ down_vals = input_array[:, down_start][:, None]
1433
+
1434
+ # top transition (use reversed transition function)
1435
+ input_array[:, up_slice] = up_vals - transition_function[::-1][None, :] * (
1436
+ up_vals - value_target
1437
+ )
1438
+
1439
+ # bottom transition (forward transition function)
1440
+ input_array[:, down_slice] = down_vals - transition_function[None, :] * (
1441
+ down_vals - value_target
1442
+ )
1443
+ elif axis == 2:
1444
+ input_array[:, :, : self.m_spatial_order + layer_offset + layer_thickness] = (
1445
+ value_target
1446
+ )
1447
+ input_array[
1448
+ :,
1449
+ :,
1450
+ n_axis_extended - self.m_spatial_order - layer_thickness - layer_offset :,
1451
+ ] = value_target
1452
+ if is_3d:
1453
+ up_start = self.m_spatial_order + layer_offset - 1
1454
+ up_end = self.m_spatial_order + layer_offset + layer_thickness
1455
+ up_slice = slice(up_start, up_end)
1456
+
1457
+ down_start = (
1458
+ n_axis_extended - self.m_spatial_order - layer_thickness - layer_offset - 1
1459
+ )
1460
+ down_end = n_axis_extended - self.m_spatial_order - layer_offset
1461
+ down_slice = slice(down_start, down_end)
1462
+
1463
+ # fetch the “mid” and “down” face values and expand dims for broadcasting
1464
+ up_vals = input_array[:, :, up_end][..., None]
1465
+ down_vals = input_array[:, :, down_start][..., None]
1466
+
1467
+ # top transition (use reversed transition function)
1468
+ input_array[:, :, up_slice] = up_vals - transition_function[::-1][
1469
+ None,
1470
+ None,
1471
+ :,
1472
+ ] * (up_vals - value_target)
1473
+
1474
+ # bottom transition (forward transition function)
1475
+ input_array[:, :, down_slice] = down_vals - transition_function[None, None, :] * (
1476
+ down_vals - value_target
1477
+ )
1478
+ else:
1479
+ error_msg = (
1480
+ "axis=2 is not supported for 2D cases. Please set is_3d=True to use axis=2."
1481
+ )
1482
+ raise ValueError(error_msg)
1483
+ else:
1484
+ error_msg = f"Invalid axis value. Expected 0, 1, but got {axis}."
1485
+ raise ValueError(error_msg)
1486
+ return input_array
1487
+
1488
+ @staticmethod
1489
+ def _calc_time_constants(
1490
+ dx: NDArray[np.float64],
1491
+ kappa: NDArray[np.float64],
1492
+ alpha: NDArray[np.float64],
1493
+ ) -> NDArray[np.float64]:
1494
+ return dx / kappa + alpha
1495
+
1496
+ def _sort_relaxation_param_dict(
1497
+ self,
1498
+ relaxation_param_dict: dict[str, NDArray[np.float64]],
1499
+ relaxation_param_updates: dict[str, NDArray[np.float64]],
1500
+ n_relaxation_mechanisms: int,
1501
+ ) -> dict:
1502
+ kappa_x1 = relaxation_param_updates["kappa_x1"]
1503
+ kappa_x2 = relaxation_param_updates["kappa_x2"]
1504
+
1505
+ d_x1 = []
1506
+ alpha_x1 = []
1507
+ d_x2 = []
1508
+ alpha_x2 = []
1509
+ time_const_x1 = []
1510
+ time_const_x2 = []
1511
+ for nu in range(1, n_relaxation_mechanisms + 1):
1512
+ d_x1_nu = relaxation_param_updates[f"d_x1_nu{nu}"]
1513
+ alpha_x1_nu = relaxation_param_updates[f"alpha_x1_nu{nu}"]
1514
+ d_x2_nu = relaxation_param_updates[f"d_x2_nu{nu}"]
1515
+ alpha_x2_nu = relaxation_param_updates[f"alpha_x2_nu{nu}"]
1516
+
1517
+ d_x1.append(d_x1_nu)
1518
+ alpha_x1.append(alpha_x1_nu)
1519
+ d_x2.append(d_x2_nu)
1520
+ alpha_x2.append(alpha_x2_nu)
1521
+
1522
+ time_const_x1_nu = self._calc_time_constants(
1523
+ dx=d_x1_nu,
1524
+ kappa=kappa_x1,
1525
+ alpha=alpha_x1_nu,
1526
+ )
1527
+ time_const_x2_nu = self._calc_time_constants(
1528
+ dx=d_x2_nu,
1529
+ kappa=kappa_x2,
1530
+ alpha=alpha_x2_nu,
1531
+ )
1532
+ time_const_x1.append(time_const_x1_nu)
1533
+ time_const_x2.append(time_const_x2_nu)
1534
+
1535
+ time_const_x1 = np.stack(time_const_x1, axis=-1)
1536
+ time_const_x2 = np.stack(time_const_x2, axis=-1)
1537
+ d_x1 = np.stack(d_x1, axis=-1)
1538
+ alpha_x1 = np.stack(alpha_x1, axis=-1)
1539
+ d_x2 = np.stack(d_x2, axis=-1)
1540
+ alpha_x2 = np.stack(alpha_x2, axis=-1)
1541
+
1542
+ # sort the nu values based on the time constants
1543
+ sorted_indices_x1 = np.argsort(time_const_x1, axis=-1)
1544
+ sorted_indices_x2 = np.argsort(time_const_x2, axis=-1)
1545
+ relaxation_param_dict["kappa_x1"] = np.atleast_2d(kappa_x1)
1546
+ relaxation_param_dict["kappa_x2"] = np.atleast_2d(kappa_x2)
1547
+
1548
+ for nu in range(1, n_relaxation_mechanisms + 1):
1549
+ relaxation_param_dict[f"d_x1_nu{nu}"] = np.atleast_2d(
1550
+ np.take_along_axis(
1551
+ d_x1,
1552
+ np.expand_dims(sorted_indices_x1[..., nu - 1], axis=-1),
1553
+ axis=-1,
1554
+ ).squeeze(-1),
1555
+ )
1556
+ relaxation_param_dict[f"alpha_x1_nu{nu}"] = np.atleast_2d(
1557
+ np.take_along_axis(
1558
+ alpha_x1,
1559
+ np.expand_dims(sorted_indices_x1[..., nu - 1], axis=-1),
1560
+ axis=-1,
1561
+ ).squeeze(-1),
1562
+ )
1563
+ relaxation_param_dict[f"d_x2_nu{nu}"] = np.atleast_2d(
1564
+ np.take_along_axis(
1565
+ d_x2,
1566
+ np.expand_dims(sorted_indices_x2[..., nu - 1], axis=-1),
1567
+ axis=-1,
1568
+ ).squeeze(-1),
1569
+ )
1570
+ relaxation_param_dict[f"alpha_x2_nu{nu}"] = np.atleast_2d(
1571
+ np.take_along_axis(
1572
+ alpha_x2,
1573
+ np.expand_dims(sorted_indices_x2[..., nu - 1], axis=-1),
1574
+ axis=-1,
1575
+ ).squeeze(-1),
1576
+ )
1577
+ return relaxation_param_dict
1578
+
1579
+ def plot(
1580
+ self,
1581
+ export_path: Path | str | None = Path("./temp/temp.png"),
1582
+ *,
1583
+ show: bool = False,
1584
+ ) -> None:
1585
+ """Plot the medium fields using matplotlib."""
1586
+ relaxation_param_dict_keys = initialize_relaxation_param_dict().keys()
1587
+
1588
+ target_map_dict: OrderedDict = OrderedDict(
1589
+ [
1590
+ ("Sound speed", self.extended_medium.sound_speed),
1591
+ ("Density", self.extended_medium.density),
1592
+ ("Beta", self.extended_medium.beta),
1593
+ ("Air map", self.extended_medium.air_map),
1594
+ ],
1595
+ )
1596
+ for key in relaxation_param_dict_keys:
1597
+ target_map_dict[key] = self.extended_medium.relaxation_param_dict[key]
1598
+
1599
+ target_map_dict.update(
1600
+ [
1601
+ ("PML mask x", self.pml_mask_x),
1602
+ ("PML mask y", self.pml_mask_y),
1603
+ ("Source mask", self.extended_source.mask),
1604
+ ("Sensor mask", self.extended_sensor.mask),
1605
+ ],
1606
+ )
1607
+
1608
+ num_plots = len(target_map_dict)
1609
+ # calculate subplot shape to make a square
1610
+ n_rows = int(np.sqrt(num_plots))
1611
+ n_cols = int(np.ceil(num_plots / n_rows))
1612
+ # adjust the fig size
1613
+ fig_size = (n_cols * 5, n_rows * 5)
1614
+
1615
+ plt.close("all")
1616
+ _, axes = plt.subplots(n_rows, n_cols, figsize=fig_size)
1617
+
1618
+ for ax, (title, map_data) in zip(
1619
+ axes.flatten(),
1620
+ target_map_dict.items(),
1621
+ strict=False,
1622
+ ):
1623
+ plot_utils.plot_array_on_ax(
1624
+ ax,
1625
+ map_data,
1626
+ title=title,
1627
+ xlim=(-5, self.extended_grid.ny + 5),
1628
+ ylim=(-5, self.extended_grid.nx + 5),
1629
+ reverse_y_axis=True,
1630
+ )
1631
+ plt.tight_layout()
1632
+
1633
+ if export_path is not None:
1634
+ plt.savefig(export_path, dpi=300)
1635
+ if show:
1636
+ plt.show()
1637
+ plt.close("all")
1638
+
1639
+
1640
+ @dataclass
1641
+ class PMLBuilderExponentialAttenuation(PMLBuilder):
1642
+ """A class to set up PML for exponential attenuation media."""
1643
+
1644
+ def __init__(
1645
+ self,
1646
+ grid: fullwave.Grid,
1647
+ medium: fullwave.Medium,
1648
+ source: fullwave.Source,
1649
+ sensor: fullwave.Sensor,
1650
+ *,
1651
+ m_spatial_order: int = 8,
1652
+ n_pml_layer: int = 40,
1653
+ # n_transition_layer: int = 40,
1654
+ # pml_alpha_target: float = 1.1,
1655
+ # pml_alpha_power_target: float = 1.6,
1656
+ # pml_strength_factor: float = 2.0,
1657
+ # use_2_relax_mechanisms: bool = False,
1658
+ ) -> None:
1659
+ """Initialize the PMLSetup with the given medium, source, sensor, and PML parameters.
1660
+
1661
+ Parameters
1662
+ ----------
1663
+ grid: fullwave.Grid
1664
+ The grid configuration.
1665
+ medium : fullwave.Medium)
1666
+ The medium relaxation maps.
1667
+ source : fullwave.Source
1668
+ The source configuration.
1669
+ sensor : fullwave.Sensor
1670
+ The sensor configuration.
1671
+ m_spatial_order : int, optional
1672
+ fullwave simulation's spatial order (default is 8).
1673
+ It depends on the fullwave simulation binary version.
1674
+ Fullwave simulation has 2M th order spatial accuracy and fourth order accuracy in time.
1675
+ see Pinton, G. (2021) http://arxiv.org/abs/2106.11476 for more detail.
1676
+ n_pml_layer : int, optional
1677
+ PML layer thickness (default is 40).
1678
+ n_transition_layer : int, optional
1679
+ Number of transition layers (default is 40).
1680
+ pml_alpha_target : float, optional
1681
+ Target alpha value for PML (default is 0.5).
1682
+ This value is used to calculate the transition layer values.
1683
+ pml_alpha_power_target : float, optional
1684
+ Target alpha power value for PML (default is 1.0).
1685
+ This value is used to calculate the transition layer values.
1686
+ pml_strength_factor : float, optional
1687
+ Strength factor for PML (default is 2.0).
1688
+ This value is used to calculate the PML target values.
1689
+ use_2_relax_mechanisms : bool, optional
1690
+ If True, use 2 relaxation mechanisms for PML for stability (default is False).
1691
+ if True, pml_alpha_target, pml_alpha_power_target, and pml_strength_factor are ignored.
1692
+
1693
+ """
1694
+ check_functions.check_instance(
1695
+ grid,
1696
+ fullwave.Grid,
1697
+ )
1698
+ check_functions.check_instance(
1699
+ medium,
1700
+ fullwave.Medium,
1701
+ )
1702
+ check_functions.check_instance(
1703
+ source,
1704
+ fullwave.Source,
1705
+ )
1706
+ check_functions.check_instance(
1707
+ sensor,
1708
+ fullwave.Sensor,
1709
+ )
1710
+
1711
+ self.grid_org = grid
1712
+ self.medium_org = medium
1713
+ self.source_org = source
1714
+ self.sensor_org = sensor
1715
+ self.is_3d = grid.is_3d
1716
+
1717
+ self.m_spatial_order = m_spatial_order
1718
+ self.n_pml_layer = n_pml_layer
1719
+ # self.n_transition_layer = n_transition_layer
1720
+ # self.pml_alpha_target = pml_alpha_target
1721
+ # self.pml_alpha_power_target = pml_alpha_power_target
1722
+ # self.pml_strength_factor = pml_strength_factor
1723
+ # self.use_2_relax_mechanisms = use_2_relax_mechanisms
1724
+
1725
+ domain_size: tuple[float, ...]
1726
+ if self.is_3d:
1727
+ domain_size = (
1728
+ (self.medium_org.sound_speed.shape[0] + 2 * self.num_boundary_points)
1729
+ * self.grid_org.dx,
1730
+ (self.medium_org.sound_speed.shape[1] + 2 * self.num_boundary_points)
1731
+ * self.grid_org.dy,
1732
+ (self.medium_org.sound_speed.shape[2] + 2 * self.num_boundary_points)
1733
+ * self.grid_org.dz,
1734
+ )
1735
+ else:
1736
+ domain_size = (
1737
+ (self.medium_org.sound_speed.shape[0] + 2 * self.num_boundary_points)
1738
+ * self.grid_org.dx,
1739
+ (self.medium_org.sound_speed.shape[1] + 2 * self.num_boundary_points)
1740
+ * self.grid_org.dy,
1741
+ )
1742
+ self.extended_grid = fullwave.Grid(
1743
+ domain_size=domain_size,
1744
+ f0=self.grid_org.f0,
1745
+ duration=self.grid_org.duration,
1746
+ c0=self.grid_org.c0,
1747
+ ppw=self.grid_org.ppw,
1748
+ cfl=self.grid_org.cfl,
1749
+ )
1750
+
1751
+ self.extended_medium = fullwave.Medium(
1752
+ grid=self.extended_grid,
1753
+ sound_speed=self._extend_map_for_pml(self.medium_org.sound_speed),
1754
+ density=self._extend_map_for_pml(self.medium_org.density),
1755
+ beta=self._extend_map_for_pml(self.medium_org.beta),
1756
+ alpha_coeff=self._extend_map_for_pml(self.medium_org.alpha_coeff),
1757
+ alpha_power=self._extend_map_for_pml(self.medium_org.alpha_power),
1758
+ air_map=self._extend_map_for_pml(self.medium_org.air_map),
1759
+ n_relaxation_mechanisms=self.medium_org.n_relaxation_mechanisms,
1760
+ path_relaxation_parameters_database=self.medium_org.path_relaxation_parameters_database,
1761
+ attenuation_builder=self.medium_org.attenuation_builder,
1762
+ )
1763
+
1764
+ self.extended_source = fullwave.Source(
1765
+ p0=self.source_org.p0,
1766
+ mask=self._extend_map_for_pml(self.source_org.mask, fill_edge=False),
1767
+ )
1768
+ self.extended_sensor = fullwave.Sensor(
1769
+ mask=self._extend_map_for_pml(self.sensor_org.mask, fill_edge=False),
1770
+ sampling_modulus_time=self.sensor_org.sampling_modulus_time,
1771
+ )
1772
+ if self.is_3d:
1773
+ self.pml_mask_x, self.pml_mask_y, self.pml_mask_z = self._localize_pml_region()
1774
+ else:
1775
+ self.pml_mask_x, self.pml_mask_y = self._localize_pml_region()
1776
+
1777
+ self.pml_layer_m = self.extended_grid.dx * self.n_pml_layer
1778
+ # self.transition_layer_m = self.extended_grid.dx * self.n_transition_layer
1779
+
1780
+ self.n_polynomial = 2
1781
+ self.theoritical_reflection_coefficient = 10 ** (-30)
1782
+
1783
+ if self.n_pml_layer == 0:
1784
+ self.n_transition_layer = 0
1785
+
1786
+ @cached_property
1787
+ def num_boundary_points(self) -> int:
1788
+ """Returns the number of the boundary points.
1789
+
1790
+ Number of PML layer and ghost cells.
1791
+ """
1792
+ return self.n_pml_layer + self.m_spatial_order
1793
+
1794
+ def run(self, *, use_pml: bool = True) -> fullwave.MediumExponentialAttenuation:
1795
+ """Generate perfect matched layer (PML) relaxation parameters.
1796
+
1797
+ It generates the relaxation parameters
1798
+ for the PML region considering the given medium and PML parameters.
1799
+
1800
+ Returns
1801
+ -------
1802
+ Medium
1803
+ A Medium instance with the constructed domain properties.
1804
+
1805
+ """
1806
+ if use_pml:
1807
+ extended_medium: fullwave.MediumExponentialAttenuation = (
1808
+ self.extended_medium.build_exponential()
1809
+ )
1810
+ if self.is_3d:
1811
+ return self._apply_pml_3d(
1812
+ extended_medium=extended_medium,
1813
+ )
1814
+
1815
+ return self._apply_pml_2d(
1816
+ extended_medium=extended_medium,
1817
+ )
1818
+
1819
+ extended_medium: fullwave.MediumExponentialAttenuation = (
1820
+ self.extended_medium.build_exponential()
1821
+ )
1822
+ return extended_medium
1823
+
1824
+ @staticmethod
1825
+ def _mask_body_2d(nx: int, ny: int, n_body: int) -> NDArray[np.float64]:
1826
+ """Create a mask for the PML region.
1827
+
1828
+ Parameters
1829
+ ----------
1830
+ nx : int
1831
+ Number of grid points in the x-direction.
1832
+ ny : int
1833
+ Number of grid points in the y-direction.
1834
+ n_body : int
1835
+ Thickness of the body region (non-PML region).
1836
+
1837
+ Returns
1838
+ -------
1839
+ NDArray[np.float64]
1840
+ A 3D numpy array representing the PML mask.
1841
+
1842
+ """
1843
+ # Create coordinate grids (1-based indices like MATLAB)
1844
+ x = np.arange(1, nx + 1)[:, None]
1845
+ y = np.arange(1, ny + 1)[None, :]
1846
+
1847
+ # Distances from each side boundary
1848
+ ri = np.where(x <= n_body, n_body - x + 1, np.where(x > nx - n_body, x - (nx - n_body), 0))
1849
+ rj = np.where(y <= n_body, n_body - y + 1, np.where(y > ny - n_body, y - (ny - n_body), 0))
1850
+
1851
+ # Compute mask
1852
+ mask = np.sqrt(ri**2 + rj**2)
1853
+
1854
+ # Normalize
1855
+ if mask.max() > 0:
1856
+ mask /= mask.max()
1857
+
1858
+ return mask
1859
+
1860
+ @staticmethod
1861
+ def _mask_body_3d(nx: int, ny: int, nz: int, n_body: int) -> NDArray[np.float64]:
1862
+ """Create a mask for the PML region.
1863
+
1864
+ Parameters
1865
+ ----------
1866
+ nx : int
1867
+ Number of grid points in the x-direction.
1868
+ ny : int
1869
+ Number of grid points in the y-direction.
1870
+ nz : int
1871
+ Number of grid points in the z-direction.
1872
+ n_body : int
1873
+ Thickness of the body region (non-PML region).
1874
+
1875
+ Returns
1876
+ -------
1877
+ NDArray[np.float64]
1878
+ A 3D numpy array representing the PML mask.
1879
+
1880
+ """
1881
+ # Create coordinate grids (1-based indices like MATLAB)
1882
+ x = np.arange(1, nx + 1)[:, None, None]
1883
+ y = np.arange(1, ny + 1)[None, :, None]
1884
+ z = np.arange(1, nz + 1)[None, None, :]
1885
+
1886
+ # Distances from each side boundary
1887
+ ri = np.where(x <= n_body, n_body - x + 1, np.where(x > nx - n_body, x - (nx - n_body), 0))
1888
+ rj = np.where(y <= n_body, n_body - y + 1, np.where(y > ny - n_body, y - (ny - n_body), 0))
1889
+ rk = np.where(z <= n_body, n_body - z + 1, np.where(z > nz - n_body, z - (nz - n_body), 0))
1890
+
1891
+ # Compute mask
1892
+ mask = np.sqrt(ri**2 + rj**2 + rk**2)
1893
+
1894
+ # Normalize
1895
+ if mask.max() > 0:
1896
+ mask /= mask.max()
1897
+
1898
+ return mask
1899
+
1900
+ def _apply_pml_3d(
1901
+ self,
1902
+ extended_medium: fullwave.MediumExponentialAttenuation,
1903
+ ) -> fullwave.MediumExponentialAttenuation:
1904
+ a_mask = self._mask_body_3d(
1905
+ nx=extended_medium.alpha_exp.shape[0],
1906
+ ny=extended_medium.alpha_exp.shape[1],
1907
+ nz=extended_medium.alpha_exp.shape[2],
1908
+ n_body=self.num_boundary_points,
1909
+ )
1910
+ extended_medium.alpha_exp *= 1 - a_mask
1911
+ return extended_medium
1912
+
1913
+ def _apply_pml_2d(
1914
+ self,
1915
+ extended_medium: fullwave.MediumExponentialAttenuation,
1916
+ ) -> fullwave.MediumExponentialAttenuation:
1917
+ a_mask = self._mask_body_2d(
1918
+ nx=extended_medium.alpha_exp.shape[0],
1919
+ ny=extended_medium.alpha_exp.shape[1],
1920
+ n_body=self.num_boundary_points,
1921
+ )
1922
+ extended_medium.alpha_exp *= 1 - a_mask
1923
+ return extended_medium