fullwave25 1.3.1.dev0__tar.gz → 1.3.2.dev0__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.dev0}/.bumpversion.toml +1 -1
  2. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/PKG-INFO +1 -1
  3. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/__init__.py +1 -1
  4. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/solver/solver.py +183 -35
  5. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/pyproject.toml +1 -1
  6. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/uv.lock +1 -1
  7. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/.gitignore +0 -0
  8. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/.pre-commit-config.yaml +0 -0
  9. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/.python-version +0 -0
  10. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/CITATION.cff +0 -0
  11. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/CONTRIBUTING.md +0 -0
  12. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/LICENSE +0 -0
  13. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/Makefile +0 -0
  14. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/README.md +0 -0
  15. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/attenuation.png +0 -0
  16. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/beamformer.m +0 -0
  17. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/debug_solver_bin/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
  18. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu +0 -0
  19. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124 +0 -0
  20. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
  21. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124 +0 -0
  22. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
  23. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/experiments/__init__.py +0 -0
  24. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/experiments/exp_scatterer_diameter.py +0 -0
  25. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/beamformer/__init__.py +0 -0
  26. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/beamformer/beamformer.py +0 -0
  27. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/constants/__init__.py +0 -0
  28. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/constants/material_properties.py +0 -0
  29. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/grid.py +0 -0
  30. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium.py +0 -0
  31. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium_builder/__init__.py +0 -0
  32. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium_builder/domain.py +0 -0
  33. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium_builder/medium_builder.py +0 -0
  34. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium_builder/presets/__init__.py +0 -0
  35. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium_builder/presets/data/.keep +0 -0
  36. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium_builder/presets/data/abdominal_wall/i2365f_etfw1.mat +0 -0
  37. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium_builder/presets/domain_abdominal_wall.py +0 -0
  38. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium_builder/presets/domain_background.py +0 -0
  39. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium_builder/presets/domain_scatterer.py +0 -0
  40. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium_builder/presets/domain_simple.py +0 -0
  41. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/medium_builder/presets/domain_water_gel.py +0 -0
  42. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/sensor.py +0 -0
  43. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/solver/__init__.py +0 -0
  44. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/solver/binary_manager.py +0 -0
  45. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/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.dev0}/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.dev0}/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.dev0}/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.dev0}/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.dev0}/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.dev0}/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.dev0}/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.dev0}/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.dev0}/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.dev0}/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.dev0}/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.dev0}/fullwave/solver/cuda_utils.py +0 -0
  58. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/solver/input_file_writer.py +0 -0
  59. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/solver/launcher.py +0 -0
  60. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/solver/pml_builder.py +0 -0
  61. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/solver/shipped_database.py +0 -0
  62. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/solver/source_type.py +0 -0
  63. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/solver/utils.py +0 -0
  64. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/source.py +0 -0
  65. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/transducer.py +0 -0
  66. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/transmit.py +0 -0
  67. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/utils/__init__.py +0 -0
  68. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/utils/check_functions.py +0 -0
  69. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/utils/coordinates.py +0 -0
  70. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/utils/memory_tempfile.py +0 -0
  71. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/utils/numerical.py +0 -0
  72. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/utils/plot_utils.py +0 -0
  73. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/utils/pulse.py +0 -0
  74. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/utils/relaxation_parameters.py +0 -0
  75. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/utils/scatterer.py +0 -0
  76. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/utils/signal_filter.py +0 -0
  77. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/fullwave/utils/signal_process.py +0 -0
  78. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/ruff.toml +0 -0
  79. {fullwave25-1.3.1.dev0 → fullwave25-1.3.2.dev0}/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-dev0"
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-dev0
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-dev0"
70
70
 
71
71
  VERSION = __version__ # for convenience
72
72
  logger.info("Fullwave version: %s", __version__)
@@ -1197,11 +1197,21 @@ class Solver:
1197
1197
 
1198
1198
  n_source_timesteps = source.icmat.shape[1]
1199
1199
 
1200
- gb = 1024.0**3
1200
+ gib = 1024.0**3
1201
1201
 
1202
1202
  base_depth = depth // n_gpus
1203
1203
  remainder = depth % n_gpus
1204
1204
 
1205
+ box, hole = (None, None)
1206
+ if not self.use_exponential_attenuation:
1207
+ box, hole = self._relaxation_regions(medium)
1208
+ # Every GPU holds every source matrix and every zero pressure coordinate.
1209
+ source_points = sum(
1210
+ getattr(source, name, 0) or 0
1211
+ for name in ("n_sources", "n_sources_add", "n_sources_u", "n_sources_v", "n_sources_w")
1212
+ )
1213
+ body_start = 0
1214
+
1205
1215
  for rank, dev_id in enumerate(device_ids):
1206
1216
  depth_this = base_depth + (1 if rank < remainder else 0)
1207
1217
 
@@ -1213,10 +1223,12 @@ class Solver:
1213
1223
  n_halo_sides = 2
1214
1224
  local_depth = depth_this + n_halo_sides * halo_depth
1215
1225
  slab = local_depth * lateral
1226
+ low = max(body_start - (halo_depth if rank > 0 else 0), 0)
1227
+ high = min(body_start + depth_this + (halo_depth if rank < n_gpus - 1 else 0), depth)
1228
+ body_start += depth_this
1216
1229
 
1217
1230
  n_sources = max(source.n_sources // n_gpus, 0)
1218
1231
  n_sensors = max(sensor.n_sensors // n_gpus, 0)
1219
- n_air_local = max(medium.n_air // n_gpus, 0)
1220
1232
 
1221
1233
  if self.use_exponential_attenuation:
1222
1234
  total = self._mem_exponential(
@@ -1231,29 +1243,32 @@ class Solver:
1231
1243
  is_3d=self.is_3d,
1232
1244
  )
1233
1245
  else:
1246
+ band, interior = self._band_and_interior(low, high, grid.shape, box, hole)
1234
1247
  total = self._mem_relaxation(
1235
1248
  slab,
1236
1249
  n_deriv_levels,
1237
- n_sources,
1250
+ source_points,
1238
1251
  n_source_timesteps,
1239
1252
  save_gpu_memory=self.save_gpu_memory,
1240
- n_air=n_air_local,
1253
+ n_air=medium.n_air,
1241
1254
  n_sensors=n_sensors,
1242
1255
  n_relax=self.n_relax_mechanisms,
1243
1256
  float_bytes=float_bytes,
1244
1257
  int_bytes=int_bytes,
1245
1258
  is_3d=self.is_3d,
1259
+ band=band,
1260
+ interior=interior,
1246
1261
  )
1247
1262
 
1248
1263
  mode = "exponential" if self.use_exponential_attenuation else "relaxation"
1249
1264
  saving = ", save_gpu_memory=True" if self.save_gpu_memory else ""
1250
1265
  logger.info(
1251
1266
  "GPU memory estimate [GPU %s] (%s mode%s): "
1252
- "%.2f GB (depth=%d +%d halo, lateral=%d)",
1267
+ "%.2f GiB (depth=%d +%d halo, lateral=%d)",
1253
1268
  dev_id.strip(),
1254
1269
  mode,
1255
1270
  saving,
1256
- total / gb,
1271
+ total / gib,
1257
1272
  depth_this,
1258
1273
  n_halo_sides * halo_depth,
1259
1274
  lateral,
@@ -1337,8 +1352,20 @@ class Solver:
1337
1352
  float_bytes: int,
1338
1353
  int_bytes: int,
1339
1354
  is_3d: bool,
1355
+ band: int | None = None,
1356
+ interior: int | None = None,
1340
1357
  ) -> int:
1341
- """Return estimated GPU bytes for relaxation (power-law) solver.
1358
+ """Return estimated GPU bytes for the relaxation (power-law) solver.
1359
+
1360
+ The count follows the allocation of the region trimmed kernel, the same
1361
+ count as ``nrelax_device_bytes`` in the solver's ``gpu_memory_nrelax.cuh``.
1362
+ Over the whole slab the card holds the pressure and one velocity for each
1363
+ axis at two time levels, ``kappa_x2``, ``rho``, ``K``, the two finite
1364
+ amplitude maps and ``dcmap``, and mechanism 1 of the pressure memory, its
1365
+ ``a``, ``b`` and one memory variable for each axis. The mechanisms above
1366
+ the first hold the same over the interior box alone, and the momentum
1367
+ absorbing layer holds its ``a``, ``b`` and one memory variable for each
1368
+ axis over the band around the hole alone.
1342
1369
 
1343
1370
  Parameters
1344
1371
  ----------
@@ -1347,7 +1374,7 @@ class Solver:
1347
1374
  n_deriv_levels : int
1348
1375
  Number of derivative-map levels.
1349
1376
  n_sources : int
1350
- Approximate source count on this GPU.
1377
+ Source points this GPU holds, summed over every source kind.
1351
1378
  n_source_timesteps : int
1352
1379
  Number of source time steps.
1353
1380
  save_gpu_memory : bool
@@ -1362,8 +1389,13 @@ class Solver:
1362
1389
  Bytes per float (4).
1363
1390
  int_bytes : int
1364
1391
  Bytes per int (4).
1365
- is_3d: bool,
1366
- Whether the simulation is 3D (affects sensor memory).
1392
+ is_3d : bool
1393
+ Whether the simulation is 3D.
1394
+ band : int | None
1395
+ Grid points of the momentum layer band on this GPU. None takes the slab.
1396
+ interior : int | None
1397
+ Grid points of this GPU's share of the interior box of the mechanisms
1398
+ above the first. None takes the slab.
1367
1399
 
1368
1400
  Returns
1369
1401
  -------
@@ -1373,36 +1405,152 @@ class Solver:
1373
1405
  """
1374
1406
  fb = float_bytes
1375
1407
  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)
1408
+ axes = 3 if is_3d else 2
1409
+ band = slab if band is None else band
1410
+ interior = slab if interior is None else interior
1411
+ per_mechanism = 2 + axes
1412
+
1413
+ mem = 2 * (axes + 1) * slab * fb
1414
+ mem += 5 * slab * fb
1415
+ mem += slab * ib
1416
+ mem += per_mechanism * slab * fb
1417
+ mem += per_mechanism * max(n_relax - 1, 0) * interior * fb
1418
+ mem += per_mechanism * band * fb
1419
+ mem += 9 * 2 * n_deriv_levels * fb
1392
1420
  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
1421
+ samples = n_sources if save_gpu_memory else n_sources * n_source_timesteps
1422
+ mem += samples * fb + axes * n_sources * ib
1397
1423
  if n_air > 0:
1398
- mem += ndim * n_air * ib
1399
- # sensor
1424
+ mem += axes * n_air * ib
1400
1425
  if n_sensors > 0:
1401
- mem += n_sensors * fb
1402
- mem += (ndim + 1) * n_sensors * ib
1403
- mem += n_sensors * ib
1426
+ mem += n_sensors * (fb + (axes + 2) * ib)
1404
1427
  return mem
1405
1428
 
1429
+ @staticmethod
1430
+ def _center_line_span(values: NDArray, axis: int, *, zeros: bool) -> tuple[int, int]:
1431
+ """Return the first and one past the last index of a map's center line along one axis.
1432
+
1433
+ Parameters
1434
+ ----------
1435
+ values : NDArray
1436
+ The map, on the host or on the GPU.
1437
+ axis : int
1438
+ The axis the line runs along. The other axes sit at their centers.
1439
+ zeros : bool
1440
+ True spans the zero cells, False the nonzero cells.
1441
+
1442
+ Returns
1443
+ -------
1444
+ tuple[int, int]
1445
+ The span, empty as ``(0, 0)`` when no cell matches.
1446
+
1447
+ """
1448
+ index = tuple(
1449
+ slice(None) if a == axis else size // 2 for a, size in enumerate(values.shape)
1450
+ )
1451
+ line = values[index]
1452
+ line = line.get() if hasattr(line, "get") else np.asarray(line)
1453
+ found = np.flatnonzero(line == 0 if zeros else line != 0)
1454
+ if found.size == 0:
1455
+ return (0, 0)
1456
+ return (int(found[0]), int(found[-1]) + 1)
1457
+
1458
+ def _relaxation_regions(
1459
+ self,
1460
+ medium: fullwave.MediumRelaxationMaps,
1461
+ ) -> tuple[list[tuple[int, int]] | None, list[tuple[int, int]] | None]:
1462
+ """Return the interior box and the momentum layer hole the kernel will measure.
1463
+
1464
+ The kernel stores every mechanism above the first over the box where its
1465
+ ``a`` is nonzero, and the momentum absorbing layer over the band around the
1466
+ box where the first momentum ``a`` is zero. Both are read here along the
1467
+ center lines of each axis, which is how the kernel finds the hole, and a
1468
+ clean box shape is assumed for both.
1469
+
1470
+ Parameters
1471
+ ----------
1472
+ medium : fullwave.MediumRelaxationMaps
1473
+ The extended medium after the absorbing layer is built.
1474
+
1475
+ Returns
1476
+ -------
1477
+ tuple
1478
+ The box and the hole as one ``(start, end)`` span for each axis. None
1479
+ stands for a map the medium does not carry, which counts as the whole
1480
+ slab.
1481
+
1482
+ """
1483
+ maps = medium.relaxation_param_dict_for_fw2
1484
+ box_maps = [maps.get(f"a_pml_u{nu}") for nu in range(2, self.n_relax_mechanisms + 1)]
1485
+ box_maps = [values for values in box_maps if values is not None]
1486
+ box = None
1487
+ if self.n_relax_mechanisms > 1 and len(box_maps) == self.n_relax_mechanisms - 1:
1488
+ box = []
1489
+ for axis in range(box_maps[0].ndim):
1490
+ spans = [self._center_line_span(values, axis, zeros=False) for values in box_maps]
1491
+ spans = [span for span in spans if span[1] > span[0]]
1492
+ box.append(
1493
+ (min(s for s, _ in spans), max(e for _, e in spans)) if spans else (0, 0)
1494
+ )
1495
+ hole = None
1496
+ if maps.get("a_pml_x1") is not None:
1497
+ values = maps["a_pml_x1"]
1498
+ hole = [self._center_line_span(values, axis, zeros=True) for axis in range(values.ndim)]
1499
+ if any(end <= start for start, end in hole):
1500
+ hole = [(0, 0)] * values.ndim
1501
+ return box, hole
1502
+
1503
+ @staticmethod
1504
+ def _band_and_interior(
1505
+ low: int,
1506
+ high: int,
1507
+ shape: tuple[int, ...],
1508
+ box: list[tuple[int, int]] | None,
1509
+ hole: list[tuple[int, int]] | None,
1510
+ ) -> tuple[int, int]:
1511
+ """Return one GPU's band and interior box cells, as the kernel counts them.
1512
+
1513
+ Parameters
1514
+ ----------
1515
+ low : int
1516
+ The first grid row the GPU holds, its halo included.
1517
+ high : int
1518
+ One past the last grid row the GPU holds, its halo included.
1519
+ shape : tuple[int, ...]
1520
+ The extended grid shape.
1521
+ box : list[tuple[int, int]] | None
1522
+ The interior box, one span for each axis, or None for the whole slab.
1523
+ hole : list[tuple[int, int]] | None
1524
+ The momentum layer hole, one span for each axis, or None for the whole slab.
1525
+
1526
+ Returns
1527
+ -------
1528
+ tuple[int, int]
1529
+ The band cells and the interior cells of this GPU.
1530
+
1531
+ """
1532
+ rows = high - low
1533
+ lateral = int(np.prod(shape[1:]))
1534
+ if box is None:
1535
+ interior = rows * lateral
1536
+ else:
1537
+ box_rows = max(0, min(high, box[0][1]) - max(low, box[0][0]))
1538
+ interior = box_rows * int(np.prod([end - start for start, end in box[1:]]))
1539
+ if hole is None:
1540
+ return rows * lateral, interior
1541
+ hole_low = min(max(hole[0][0] - low, 0), rows)
1542
+ hole_high = max(min(max(hole[0][1] - low, 0), rows), hole_low)
1543
+ hole_rows = hole_high - hole_low
1544
+ band = (rows - hole_rows) * lateral
1545
+ hole_columns = hole[1][1] - hole[1][0]
1546
+ strip_columns = shape[1] - hole_columns
1547
+ if len(shape) == 3:
1548
+ strip_depths = shape[2] - (hole[2][1] - hole[2][0])
1549
+ band += hole_rows * (strip_columns * shape[2] + hole_columns * strip_depths)
1550
+ else:
1551
+ band += hole_rows * strip_columns
1552
+ return band, interior
1553
+
1406
1554
  def print_info(self) -> None:
1407
1555
  """Print the Solver instance information."""
1408
1556
  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-dev0" # 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.dev0"
765
765
  source = { editable = "." }
766
766
  dependencies = [
767
767
  { name = "cupy-cuda12x" },
File without changes