fullwave25 1.3.1.dev0__tar.gz → 1.3.2__tar.gz

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.
Files changed (79) hide show
  1. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/.bumpversion.toml +1 -1
  2. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/PKG-INFO +1 -1
  3. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/__init__.py +1 -1
  4. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/binary_manager.py +1 -1
  5. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/solver.py +259 -75
  6. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/pyproject.toml +1 -1
  7. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/uv.lock +1 -1
  8. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/.gitignore +0 -0
  9. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/.pre-commit-config.yaml +0 -0
  10. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/.python-version +0 -0
  11. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/CITATION.cff +0 -0
  12. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/CONTRIBUTING.md +0 -0
  13. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/LICENSE +0 -0
  14. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/Makefile +0 -0
  15. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/README.md +0 -0
  16. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/attenuation.png +0 -0
  17. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/beamformer.m +0 -0
  18. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
  19. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu +0 -0
  20. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124 +0 -0
  21. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
  22. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124 +0 -0
  23. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
  24. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/experiments/__init__.py +0 -0
  25. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/experiments/exp_scatterer_diameter.py +0 -0
  26. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/beamformer/__init__.py +0 -0
  27. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/beamformer/beamformer.py +0 -0
  28. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/constants/__init__.py +0 -0
  29. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/constants/material_properties.py +0 -0
  30. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/grid.py +0 -0
  31. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium.py +0 -0
  32. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/__init__.py +0 -0
  33. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/domain.py +0 -0
  34. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/medium_builder.py +0 -0
  35. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/__init__.py +0 -0
  36. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/data/.keep +0 -0
  37. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/data/abdominal_wall/i2365f_etfw1.mat +0 -0
  38. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/domain_abdominal_wall.py +0 -0
  39. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/domain_background.py +0 -0
  40. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/domain_scatterer.py +0 -0
  41. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/domain_simple.py +0 -0
  42. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/domain_water_gel.py +0 -0
  43. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/sensor.py +0 -0
  44. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/__init__.py +0 -0
  45. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda118 +0 -0
  46. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
  47. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda129 +0 -0
  48. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda130 +0 -0
  49. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda118 +0 -0
  50. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda124 +0 -0
  51. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda129 +0 -0
  52. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda130 +0 -0
  53. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/database/relaxation_params_database_num_relax=3_20260828_1513.mat +0 -0
  54. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/database/relaxation_params_database_num_relax=3_20260828_1513_invalid_cells.json +0 -0
  55. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/database/relaxation_params_database_num_relax=4_20260828_1456.mat +0 -0
  56. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/database/relaxation_params_database_num_relax=4_20260828_1456_invalid_cells.json +0 -0
  57. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/cuda_utils.py +0 -0
  58. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/input_file_writer.py +0 -0
  59. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/launcher.py +0 -0
  60. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/pml_builder.py +0 -0
  61. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/shipped_database.py +0 -0
  62. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/source_type.py +0 -0
  63. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/solver/utils.py +0 -0
  64. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/source.py +0 -0
  65. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/transducer.py +0 -0
  66. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/transmit.py +0 -0
  67. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/utils/__init__.py +0 -0
  68. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/utils/check_functions.py +0 -0
  69. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/utils/coordinates.py +0 -0
  70. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/utils/memory_tempfile.py +0 -0
  71. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/utils/numerical.py +0 -0
  72. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/utils/plot_utils.py +0 -0
  73. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/utils/pulse.py +0 -0
  74. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/utils/relaxation_parameters.py +0 -0
  75. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/utils/scatterer.py +0 -0
  76. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/utils/signal_filter.py +0 -0
  77. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/fullwave/utils/signal_process.py +0 -0
  78. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/ruff.toml +0 -0
  79. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2}/signal_filter_example.png +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "1.3.1-dev0"
2
+ current_version = "1.3.2"
3
3
  parse = """(?x)
4
4
  (?P<major>0|[1-9]\\d*)\\.
5
5
  (?P<minor>0|[1-9]\\d*)\\.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fullwave25
3
- Version: 1.3.1-dev0
3
+ Version: 1.3.2
4
4
  Summary: Fullwave 2.5: Ultrasound wave propagation simulation with heterogeneous power law attenuation modelling capabilities
5
5
  Author: Masashi Sode, Gianmarco Pinton
6
6
  Maintainer: Masashi Sode
@@ -66,7 +66,7 @@ try:
66
66
  __version__ = version("fullwave")
67
67
  except PackageNotFoundError:
68
68
  # Update via bump-my-version, not manually
69
- __version__ = "1.3.1-dev0"
69
+ __version__ = "1.3.2"
70
70
 
71
71
  VERSION = __version__ # for convenience
72
72
  logger.info("Fullwave version: %s", __version__)
@@ -28,7 +28,7 @@ CACHE_DIR = Path.home() / ".cache" / "fullwave25" / "bins"
28
28
 
29
29
  # Pinned release tag for the solver binaries.
30
30
  # Update this only when new binaries are uploaded to a GitHub release.
31
- BINARY_RELEASE_TAG = "fullwave_bin_v1.4.2"
31
+ BINARY_RELEASE_TAG = "fullwave_bin_v1.4.4"
32
32
 
33
33
 
34
34
  def _download_url(filename: str, tag: str) -> str:
@@ -42,6 +42,7 @@ COMPATIBLE_CUDA_ARCHITECTURES = [
42
42
  "sm_90", # Hopper: H100, H200
43
43
  "sm_100", # Blackwell: RTX 50 series
44
44
  "sm_101", # Blackwell: RTX 50 series
45
+ "sm_103", # Blackwell
45
46
  "sm_120", # Blackwell: RTX 50 series
46
47
  "sm_121", # Blackwell: RTX 50 series
47
48
  ]
@@ -56,18 +57,6 @@ VERIFIED_CUDA_ARCHITECTURES = [
56
57
  ]
57
58
 
58
59
 
59
- COMPATIBLE_CUDA_VERSIONS = [
60
- 11.8,
61
- 12.4,
62
- 12.9,
63
- 13.0,
64
- 13.1,
65
- ]
66
-
67
- COMPATIBLE_CUDA_RANGES = [
68
- (11.8, 13.1),
69
- ]
70
-
71
60
  VERIFIED_CUDA_VERSIONS = [
72
61
  12.4,
73
62
  12.9,
@@ -108,6 +97,7 @@ COMPATIBLE_CUDA_VERSIONS_ARCHITECTURES_set = {
108
97
  (12.9, "sm_90"),
109
98
  (12.9, "sm_100"),
110
99
  (12.9, "sm_101"),
100
+ (12.9, "sm_103"),
111
101
  (12.9, "sm_120"),
112
102
  (12.9, "sm_121"),
113
103
  # ---
@@ -149,7 +139,36 @@ def _make_cuda_arch_option(*, use_gpu: bool = True) -> str:
149
139
  return arch_option
150
140
 
151
141
 
152
- def _make_cuda_version_option(*, use_gpu: bool = True) -> tuple[str, float]:
142
+ def _make_cuda_version_option(*, cuda_arch: str, use_gpu: bool = True) -> tuple[str, float]:
143
+ """Return the CUDA tag of the binary to run on this driver and architecture.
144
+
145
+ The driver reports the newest CUDA version it supports, and it also runs a binary
146
+ built with an older CUDA version. The choice is therefore the newest CUDA version
147
+ with a build for the architecture that is not newer than the driver. Where no such
148
+ build exists, the choice is the oldest build of the driver's CUDA major version,
149
+ which runs through CUDA minor version compatibility. A Blackwell GPU on a CUDA 12.8
150
+ driver takes this path, because its first build is CUDA 12.9.
151
+
152
+ Parameters
153
+ ----------
154
+ cuda_arch : str
155
+ The architecture option of the first GPU, such as "sm_90".
156
+ use_gpu : bool, optional
157
+ Whether the simulation runs on the GPU. Without it, the driver version is
158
+ returned unchecked.
159
+
160
+ Returns
161
+ -------
162
+ tuple[str, float]
163
+ The tag the binary name ends with, such as "cuda129", and its CUDA version.
164
+
165
+ Raises
166
+ ------
167
+ ValueError
168
+ If the driver version cannot be read, or the architecture has no build as
169
+ old as the driver and none of the driver's CUDA major version.
170
+
171
+ """
153
172
  cuda_version: float = retrieve_cuda_version()
154
173
  if use_gpu and cuda_version == -1:
155
174
  error_msg = (
@@ -159,34 +178,38 @@ def _make_cuda_version_option(*, use_gpu: bool = True) -> tuple[str, float]:
159
178
  logger.error(error_msg)
160
179
  raise ValueError(error_msg)
161
180
 
162
- # range check
163
- if use_gpu and not any(start <= cuda_version <= end for start, end in COMPATIBLE_CUDA_RANGES):
164
- error_msg = (
165
- f"CUDA version {cuda_version} is not in the compatible ranges: "
166
- f"{COMPATIBLE_CUDA_RANGES}. Please install a compatible CUDA version."
181
+ if use_gpu:
182
+ built_versions = sorted(
183
+ version
184
+ for version, arch in COMPATIBLE_CUDA_VERSIONS_ARCHITECTURES_set
185
+ if arch == cuda_arch
167
186
  )
168
- logger.error(error_msg)
169
- raise ValueError(error_msg)
170
-
171
- # find the a compatible cuda version below the system cuda
172
- if use_gpu and cuda_version not in COMPATIBLE_CUDA_VERSIONS:
173
- compatible_versions_below = [v for v in COMPATIBLE_CUDA_VERSIONS if v < cuda_version]
174
- if compatible_versions_below:
175
- closest_version = max(compatible_versions_below)
176
- message = (
177
- f"Warning: CUDA version {cuda_version} is not in the compatible versions: "
178
- f"{COMPATIBLE_CUDA_VERSIONS}. "
179
- f"Using the closest compatible version {closest_version} instead."
180
- )
181
- logger.warning(message)
182
- cuda_version = closest_version
187
+ older_versions = [version for version in built_versions if version <= cuda_version]
188
+ same_major_versions = [
189
+ version for version in built_versions if int(version) == int(cuda_version)
190
+ ]
191
+ if older_versions:
192
+ closest_version = older_versions[-1]
193
+ elif same_major_versions:
194
+ closest_version = same_major_versions[0]
183
195
  else:
184
196
  error_msg = (
185
- f"No compatible CUDA versions found below {cuda_version}. "
186
- "Please install a compatible CUDA version."
197
+ f"No binary for architecture {cuda_arch} runs on a driver that supports "
198
+ f"CUDA {cuda_version}. The binaries for this architecture are built with "
199
+ f"CUDA {built_versions}. Please update the NVIDIA driver."
187
200
  )
188
201
  logger.error(error_msg)
189
202
  raise ValueError(error_msg)
203
+ if closest_version != cuda_version:
204
+ message = (
205
+ f"Warning: CUDA version {cuda_version} of the driver has no binary for "
206
+ f"architecture {cuda_arch}. "
207
+ f"Using the closest compatible version {closest_version} instead."
208
+ )
209
+ if closest_version > cuda_version:
210
+ message += " It runs through CUDA minor version compatibility."
211
+ logger.warning(message)
212
+ cuda_version = closest_version
190
213
 
191
214
  if use_gpu and cuda_version not in VERIFIED_CUDA_VERSIONS:
192
215
  warning_msg = (
@@ -240,7 +263,10 @@ def _retrieve_fullwave_simulation_path(
240
263
  use_isotropic_relaxation: bool = True,
241
264
  ) -> Path:
242
265
  arch_option = _make_cuda_arch_option(use_gpu=use_gpu)
243
- cuda_version_option, cuda_version = _make_cuda_version_option(use_gpu=use_gpu)
266
+ cuda_version_option, cuda_version = _make_cuda_version_option(
267
+ use_gpu=use_gpu,
268
+ cuda_arch=arch_option,
269
+ )
244
270
  if use_isotropic_relaxation is False:
245
271
  error_msg = (
246
272
  "Currently, only isotropic relaxation is supported. "
@@ -251,10 +277,13 @@ def _retrieve_fullwave_simulation_path(
251
277
  # isotropic_str = "_isotropic" if use_isotropic_relaxation else ""
252
278
  isotropic_str = ""
253
279
 
254
- _check_compatible_set(
280
+ if use_gpu and not _check_compatible_set(
255
281
  cuda_version=cuda_version,
256
282
  cuda_arch=arch_option,
257
- )
283
+ ):
284
+ error_msg = f"No binary is built with CUDA {cuda_version} for architecture {arch_option}."
285
+ logger.error(error_msg)
286
+ raise ValueError(error_msg)
258
287
  if use_exponential_attenuation:
259
288
  if is_3d and use_gpu:
260
289
  path_fullwave_simulation_bin = (
@@ -725,15 +754,22 @@ class Solver:
725
754
  CuPy arrays, then drain both the device and pinned memory pools.
726
755
  This prevents stale allocations from causing memory pressure when
727
756
  subsequent operations allocate large GPU arrays.
757
+
758
+ The release is best effort. It does nothing when CuPy is absent, and
759
+ it does nothing when CuPy is present without a usable CUDA driver,
760
+ which is the case on a machine with no GPU.
728
761
  """
729
762
  gc.collect()
730
763
  try:
731
764
  import cupy as cp # noqa: PLC0415
765
+ except ImportError:
766
+ return
732
767
 
768
+ try:
733
769
  cp.get_default_memory_pool().free_all_blocks()
734
770
  cp.get_default_pinned_memory_pool().free_all_blocks()
735
- except ImportError:
736
- pass
771
+ except cp.cuda.runtime.CUDARuntimeError as error:
772
+ logger.debug("GPU memory pools were not released: %s", error)
737
773
 
738
774
  @staticmethod
739
775
  def _check_input(
@@ -1197,11 +1233,21 @@ class Solver:
1197
1233
 
1198
1234
  n_source_timesteps = source.icmat.shape[1]
1199
1235
 
1200
- gb = 1024.0**3
1236
+ gib = 1024.0**3
1201
1237
 
1202
1238
  base_depth = depth // n_gpus
1203
1239
  remainder = depth % n_gpus
1204
1240
 
1241
+ box, hole = (None, None)
1242
+ if not self.use_exponential_attenuation:
1243
+ box, hole = self._relaxation_regions(medium)
1244
+ # Every GPU holds every source matrix and every zero pressure coordinate.
1245
+ source_points = sum(
1246
+ getattr(source, name, 0) or 0
1247
+ for name in ("n_sources", "n_sources_add", "n_sources_u", "n_sources_v", "n_sources_w")
1248
+ )
1249
+ body_start = 0
1250
+
1205
1251
  for rank, dev_id in enumerate(device_ids):
1206
1252
  depth_this = base_depth + (1 if rank < remainder else 0)
1207
1253
 
@@ -1213,10 +1259,12 @@ class Solver:
1213
1259
  n_halo_sides = 2
1214
1260
  local_depth = depth_this + n_halo_sides * halo_depth
1215
1261
  slab = local_depth * lateral
1262
+ low = max(body_start - (halo_depth if rank > 0 else 0), 0)
1263
+ high = min(body_start + depth_this + (halo_depth if rank < n_gpus - 1 else 0), depth)
1264
+ body_start += depth_this
1216
1265
 
1217
1266
  n_sources = max(source.n_sources // n_gpus, 0)
1218
1267
  n_sensors = max(sensor.n_sensors // n_gpus, 0)
1219
- n_air_local = max(medium.n_air // n_gpus, 0)
1220
1268
 
1221
1269
  if self.use_exponential_attenuation:
1222
1270
  total = self._mem_exponential(
@@ -1231,29 +1279,32 @@ class Solver:
1231
1279
  is_3d=self.is_3d,
1232
1280
  )
1233
1281
  else:
1282
+ band, interior = self._band_and_interior(low, high, grid.shape, box, hole)
1234
1283
  total = self._mem_relaxation(
1235
1284
  slab,
1236
1285
  n_deriv_levels,
1237
- n_sources,
1286
+ source_points,
1238
1287
  n_source_timesteps,
1239
1288
  save_gpu_memory=self.save_gpu_memory,
1240
- n_air=n_air_local,
1289
+ n_air=medium.n_air,
1241
1290
  n_sensors=n_sensors,
1242
1291
  n_relax=self.n_relax_mechanisms,
1243
1292
  float_bytes=float_bytes,
1244
1293
  int_bytes=int_bytes,
1245
1294
  is_3d=self.is_3d,
1295
+ band=band,
1296
+ interior=interior,
1246
1297
  )
1247
1298
 
1248
1299
  mode = "exponential" if self.use_exponential_attenuation else "relaxation"
1249
1300
  saving = ", save_gpu_memory=True" if self.save_gpu_memory else ""
1250
1301
  logger.info(
1251
1302
  "GPU memory estimate [GPU %s] (%s mode%s): "
1252
- "%.2f GB (depth=%d +%d halo, lateral=%d)",
1303
+ "%.2f GiB (depth=%d +%d halo, lateral=%d)",
1253
1304
  dev_id.strip(),
1254
1305
  mode,
1255
1306
  saving,
1256
- total / gb,
1307
+ total / gib,
1257
1308
  depth_this,
1258
1309
  n_halo_sides * halo_depth,
1259
1310
  lateral,
@@ -1337,8 +1388,20 @@ class Solver:
1337
1388
  float_bytes: int,
1338
1389
  int_bytes: int,
1339
1390
  is_3d: bool,
1391
+ band: int | None = None,
1392
+ interior: int | None = None,
1340
1393
  ) -> int:
1341
- """Return estimated GPU bytes for relaxation (power-law) solver.
1394
+ """Return estimated GPU bytes for the relaxation (power-law) solver.
1395
+
1396
+ The count follows the allocation of the region trimmed kernel, the same
1397
+ count as ``nrelax_device_bytes`` in the solver's ``gpu_memory_nrelax.cuh``.
1398
+ Over the whole slab the card holds the pressure and one velocity for each
1399
+ axis at two time levels, ``kappa_x2``, ``rho``, ``K``, the two finite
1400
+ amplitude maps and ``dcmap``, and mechanism 1 of the pressure memory, its
1401
+ ``a``, ``b`` and one memory variable for each axis. The mechanisms above
1402
+ the first hold the same over the interior box alone, and the momentum
1403
+ absorbing layer holds its ``a``, ``b`` and one memory variable for each
1404
+ axis over the band around the hole alone.
1342
1405
 
1343
1406
  Parameters
1344
1407
  ----------
@@ -1347,7 +1410,7 @@ class Solver:
1347
1410
  n_deriv_levels : int
1348
1411
  Number of derivative-map levels.
1349
1412
  n_sources : int
1350
- Approximate source count on this GPU.
1413
+ Source points this GPU holds, summed over every source kind.
1351
1414
  n_source_timesteps : int
1352
1415
  Number of source time steps.
1353
1416
  save_gpu_memory : bool
@@ -1362,8 +1425,13 @@ class Solver:
1362
1425
  Bytes per float (4).
1363
1426
  int_bytes : int
1364
1427
  Bytes per int (4).
1365
- is_3d: bool,
1366
- Whether the simulation is 3D (affects sensor memory).
1428
+ is_3d : bool
1429
+ Whether the simulation is 3D.
1430
+ band : int | None
1431
+ Grid points of the momentum layer band on this GPU. None takes the slab.
1432
+ interior : int | None
1433
+ Grid points of this GPU's share of the interior box of the mechanisms
1434
+ above the first. None takes the slab.
1367
1435
 
1368
1436
  Returns
1369
1437
  -------
@@ -1373,36 +1441,152 @@ class Solver:
1373
1441
  """
1374
1442
  fb = float_bytes
1375
1443
  ib = int_bytes
1376
- ndim = 3 if is_3d else 2
1377
- n_fields = 4 if is_3d else 3 # p, u, [v], w
1378
-
1379
- # wave fields: n_fields pairs x 2 time levels
1380
- mem = n_fields * 2 * slab * fb
1381
- # relaxation psi:
1382
- mem += 2 * (ndim * n_relax * 2 * slab * fb)
1383
- # material: rho + K + beta
1384
- mem += 3 * slab * fb
1385
- # kappa: 2 arrays (kappa_x1, kappa_x2)
1386
- mem += 2 * slab * fb
1387
- # PML: pml_x1 + pml_x2, each has 2 * n_relax arrays
1388
- mem += 2 * (2 * n_relax) * slab * fb
1389
- # (dmap + dcmap)
1390
- mem += 9 * 2 * n_deriv_levels * fb + slab * ib
1391
- # source (icmat + coords)
1444
+ axes = 3 if is_3d else 2
1445
+ band = slab if band is None else band
1446
+ interior = slab if interior is None else interior
1447
+ per_mechanism = 2 + axes
1448
+
1449
+ mem = 2 * (axes + 1) * slab * fb
1450
+ mem += 5 * slab * fb
1451
+ mem += slab * ib
1452
+ mem += per_mechanism * slab * fb
1453
+ mem += per_mechanism * max(n_relax - 1, 0) * interior * fb
1454
+ mem += per_mechanism * band * fb
1455
+ mem += 9 * 2 * n_deriv_levels * fb
1392
1456
  if n_sources > 0:
1393
- mem += n_sources * fb if save_gpu_memory else n_sources * n_source_timesteps * fb
1394
- mem += ndim * n_sources * ib
1395
-
1396
- # air
1457
+ samples = n_sources if save_gpu_memory else n_sources * n_source_timesteps
1458
+ mem += samples * fb + axes * n_sources * ib
1397
1459
  if n_air > 0:
1398
- mem += ndim * n_air * ib
1399
- # sensor
1460
+ mem += axes * n_air * ib
1400
1461
  if n_sensors > 0:
1401
- mem += n_sensors * fb
1402
- mem += (ndim + 1) * n_sensors * ib
1403
- mem += n_sensors * ib
1462
+ mem += n_sensors * (fb + (axes + 2) * ib)
1404
1463
  return mem
1405
1464
 
1465
+ @staticmethod
1466
+ def _center_line_span(values: NDArray, axis: int, *, zeros: bool) -> tuple[int, int]:
1467
+ """Return the first and one past the last index of a map's center line along one axis.
1468
+
1469
+ Parameters
1470
+ ----------
1471
+ values : NDArray
1472
+ The map, on the host or on the GPU.
1473
+ axis : int
1474
+ The axis the line runs along. The other axes sit at their centers.
1475
+ zeros : bool
1476
+ True spans the zero cells, False the nonzero cells.
1477
+
1478
+ Returns
1479
+ -------
1480
+ tuple[int, int]
1481
+ The span, empty as ``(0, 0)`` when no cell matches.
1482
+
1483
+ """
1484
+ index = tuple(
1485
+ slice(None) if a == axis else size // 2 for a, size in enumerate(values.shape)
1486
+ )
1487
+ line = values[index]
1488
+ line = line.get() if hasattr(line, "get") else np.asarray(line)
1489
+ found = np.flatnonzero(line == 0 if zeros else line != 0)
1490
+ if found.size == 0:
1491
+ return (0, 0)
1492
+ return (int(found[0]), int(found[-1]) + 1)
1493
+
1494
+ def _relaxation_regions(
1495
+ self,
1496
+ medium: fullwave.MediumRelaxationMaps,
1497
+ ) -> tuple[list[tuple[int, int]] | None, list[tuple[int, int]] | None]:
1498
+ """Return the interior box and the momentum layer hole the kernel will measure.
1499
+
1500
+ The kernel stores every mechanism above the first over the box where its
1501
+ ``a`` is nonzero, and the momentum absorbing layer over the band around the
1502
+ box where the first momentum ``a`` is zero. Both are read here along the
1503
+ center lines of each axis, which is how the kernel finds the hole, and a
1504
+ clean box shape is assumed for both.
1505
+
1506
+ Parameters
1507
+ ----------
1508
+ medium : fullwave.MediumRelaxationMaps
1509
+ The extended medium after the absorbing layer is built.
1510
+
1511
+ Returns
1512
+ -------
1513
+ tuple
1514
+ The box and the hole as one ``(start, end)`` span for each axis. None
1515
+ stands for a map the medium does not carry, which counts as the whole
1516
+ slab.
1517
+
1518
+ """
1519
+ maps = medium.relaxation_param_dict_for_fw2
1520
+ box_maps = [maps.get(f"a_pml_u{nu}") for nu in range(2, self.n_relax_mechanisms + 1)]
1521
+ box_maps = [values for values in box_maps if values is not None]
1522
+ box = None
1523
+ if self.n_relax_mechanisms > 1 and len(box_maps) == self.n_relax_mechanisms - 1:
1524
+ box = []
1525
+ for axis in range(box_maps[0].ndim):
1526
+ spans = [self._center_line_span(values, axis, zeros=False) for values in box_maps]
1527
+ spans = [span for span in spans if span[1] > span[0]]
1528
+ box.append(
1529
+ (min(s for s, _ in spans), max(e for _, e in spans)) if spans else (0, 0)
1530
+ )
1531
+ hole = None
1532
+ if maps.get("a_pml_x1") is not None:
1533
+ values = maps["a_pml_x1"]
1534
+ hole = [self._center_line_span(values, axis, zeros=True) for axis in range(values.ndim)]
1535
+ if any(end <= start for start, end in hole):
1536
+ hole = [(0, 0)] * values.ndim
1537
+ return box, hole
1538
+
1539
+ @staticmethod
1540
+ def _band_and_interior(
1541
+ low: int,
1542
+ high: int,
1543
+ shape: tuple[int, ...],
1544
+ box: list[tuple[int, int]] | None,
1545
+ hole: list[tuple[int, int]] | None,
1546
+ ) -> tuple[int, int]:
1547
+ """Return one GPU's band and interior box cells, as the kernel counts them.
1548
+
1549
+ Parameters
1550
+ ----------
1551
+ low : int
1552
+ The first grid row the GPU holds, its halo included.
1553
+ high : int
1554
+ One past the last grid row the GPU holds, its halo included.
1555
+ shape : tuple[int, ...]
1556
+ The extended grid shape.
1557
+ box : list[tuple[int, int]] | None
1558
+ The interior box, one span for each axis, or None for the whole slab.
1559
+ hole : list[tuple[int, int]] | None
1560
+ The momentum layer hole, one span for each axis, or None for the whole slab.
1561
+
1562
+ Returns
1563
+ -------
1564
+ tuple[int, int]
1565
+ The band cells and the interior cells of this GPU.
1566
+
1567
+ """
1568
+ rows = high - low
1569
+ lateral = int(np.prod(shape[1:]))
1570
+ if box is None:
1571
+ interior = rows * lateral
1572
+ else:
1573
+ box_rows = max(0, min(high, box[0][1]) - max(low, box[0][0]))
1574
+ interior = box_rows * int(np.prod([end - start for start, end in box[1:]]))
1575
+ if hole is None:
1576
+ return rows * lateral, interior
1577
+ hole_low = min(max(hole[0][0] - low, 0), rows)
1578
+ hole_high = max(min(max(hole[0][1] - low, 0), rows), hole_low)
1579
+ hole_rows = hole_high - hole_low
1580
+ band = (rows - hole_rows) * lateral
1581
+ hole_columns = hole[1][1] - hole[1][0]
1582
+ strip_columns = shape[1] - hole_columns
1583
+ if len(shape) == 3:
1584
+ strip_depths = shape[2] - (hole[2][1] - hole[2][0])
1585
+ band += hole_rows * (strip_columns * shape[2] + hole_columns * strip_depths)
1586
+ else:
1587
+ band += hole_rows * strip_columns
1588
+ return band, interior
1589
+
1406
1590
  def print_info(self) -> None:
1407
1591
  """Print the Solver instance information."""
1408
1592
  print(str(self))
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fullwave25"
3
- version = "1.3.1-dev0" # Update via bump-my-version, not manually
3
+ version = "1.3.2" # Update via bump-my-version, not manually
4
4
  description = "Fullwave 2.5: Ultrasound wave propagation simulation with heterogeneous power law attenuation modelling capabilities"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -761,7 +761,7 @@ wheels = [
761
761
 
762
762
  [[package]]
763
763
  name = "fullwave25"
764
- version = "1.3.1.dev0"
764
+ version = "1.3.2"
765
765
  source = { editable = "." }
766
766
  dependencies = [
767
767
  { name = "cupy-cuda12x" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes