fullwave25 1.2.6.dev3__tar.gz → 1.2.6.dev5__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 (75) hide show
  1. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/.bumpversion.toml +1 -1
  2. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/PKG-INFO +1 -2
  3. fullwave25-1.2.6.dev5/debug_solver_bin/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
  4. fullwave25-1.2.6.dev5/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu +0 -0
  5. fullwave25-1.2.6.dev5/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
  6. fullwave25-1.2.6.dev5/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
  7. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/__init__.py +1 -1
  8. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/launcher.py +20 -3
  9. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/pml_builder.py +42 -46
  10. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/solver.py +258 -0
  11. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/pyproject.toml +11 -3
  12. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/ruff.toml +1 -0
  13. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/uv.lock +1 -12
  14. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/.gitignore +0 -0
  15. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/.pre-commit-config.yaml +0 -0
  16. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/.python-version +0 -0
  17. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/20260120_0844.log +0 -0
  18. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/CITATION.cff +0 -0
  19. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/CLAUDE.md +0 -0
  20. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/CONTRIBUTING.md +0 -0
  21. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/LICENSE +0 -0
  22. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/Makefile +0 -0
  23. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/README.md +0 -0
  24. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/beamformer.m +0 -0
  25. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124 +0 -0
  26. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124 +0 -0
  27. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/experiments/__init__.py +0 -0
  28. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/experiments/exp_scatterer_diameter.py +0 -0
  29. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/beamformer/__init__.py +0 -0
  30. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/beamformer/beamformer.py +0 -0
  31. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/constants/__init__.py +0 -0
  32. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/constants/material_properties.py +0 -0
  33. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/grid.py +0 -0
  34. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium.py +0 -0
  35. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium_builder/__init__.py +0 -0
  36. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium_builder/domain.py +0 -0
  37. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium_builder/medium_builder.py +0 -0
  38. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium_builder/presets/__init__.py +0 -0
  39. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium_builder/presets/data/.keep +0 -0
  40. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium_builder/presets/data/abdominal_wall/i2365f_etfw1.mat +0 -0
  41. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium_builder/presets/domain_abdominal_wall.py +0 -0
  42. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium_builder/presets/domain_background.py +0 -0
  43. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium_builder/presets/domain_scatterer.py +0 -0
  44. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium_builder/presets/domain_simple.py +0 -0
  45. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/medium_builder/presets/domain_water_gel.py +0 -0
  46. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/sensor.py +0 -0
  47. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/__init__.py +0 -0
  48. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/binary_manager.py +0 -0
  49. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda118 +0 -0
  50. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
  51. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda129 +0 -0
  52. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda130 +0 -0
  53. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda118 +0 -0
  54. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda124 +0 -0
  55. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda129 +0 -0
  56. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda130 +0 -0
  57. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/bins/database/relaxation_params_database_num_relax=2_20260113_0957.mat +0 -0
  58. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/cuda_utils.py +0 -0
  59. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/input_file_writer.py +0 -0
  60. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/solver/utils.py +0 -0
  61. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/source.py +0 -0
  62. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/transducer.py +0 -0
  63. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/utils/__init__.py +0 -0
  64. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/utils/check_functions.py +0 -0
  65. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/utils/coordinates.py +0 -0
  66. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/utils/memory_tempfile.py +0 -0
  67. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/utils/numerical.py +0 -0
  68. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/utils/plot_utils.py +0 -0
  69. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/utils/pulse.py +0 -0
  70. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/utils/relaxation_parameters.py +0 -0
  71. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/utils/scatterer.py +0 -0
  72. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/utils/signal_filter.py +0 -0
  73. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/fullwave/utils/signal_process.py +0 -0
  74. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/ruff.log +0 -0
  75. {fullwave25-1.2.6.dev3 → fullwave25-1.2.6.dev5}/signal_filter_example.png +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "1.2.6-dev3"
2
+ current_version = "1.2.6-dev5"
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.4
2
2
  Name: fullwave25
3
- Version: 1.2.6.dev3
3
+ Version: 1.2.6.dev5
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
@@ -13,7 +13,6 @@ Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Topic :: Scientific/Engineering :: Physics
15
15
  Requires-Python: >=3.10
16
- Requires-Dist: joblib>=1.5.3
17
16
  Requires-Dist: matplotlib>=3.10.7
18
17
  Requires-Dist: numba>=0.63.1
19
18
  Requires-Dist: numexpr>=2.14.1
@@ -60,7 +60,7 @@ try:
60
60
  __version__ = version("fullwave")
61
61
  except PackageNotFoundError:
62
62
  # Update via bump-my-version, not manually
63
- __version__ = "1.2.6-dev3"
63
+ __version__ = "1.2.6-dev5"
64
64
 
65
65
  VERSION = __version__ # for convenience
66
66
  logger.info("Fullwave version: %s", __version__)
@@ -31,6 +31,7 @@ class Launcher:
31
31
  use_gpu: bool = True,
32
32
  cuda_device_id: str | int | list | None = None,
33
33
  save_gpu_memory: bool = False,
34
+ verify_gpu: bool = True,
34
35
  ) -> None:
35
36
  """Initialize a FullwaveLauncher instance.
36
37
 
@@ -62,6 +63,11 @@ class Launcher:
62
63
  depending on the hardware and the simulation settings.
63
64
  useful in 3D simulations with large grid sizes
64
65
  where GPU memory is a limiting factor.
66
+ verify_gpu : bool, optional
67
+ Whether to verify that the specified CUDA devices exist on the system.
68
+ Defaults to True. Set to False when generating input files only
69
+ (``generate_input_only=True``) on a machine that may not have
70
+ the target GPUs available.
65
71
 
66
72
  """
67
73
  self._path_fullwave_simulation_bin = path_fullwave_simulation_bin
@@ -69,7 +75,10 @@ class Launcher:
69
75
  assert self._path_fullwave_simulation_bin.exists(), error_msg
70
76
  self.is_3d = is_3d
71
77
  self.use_gpu = use_gpu
72
- self.cuda_device_id = self._configure_cuda_device_id(cuda_device_id)
78
+ self.cuda_device_id = self._configure_cuda_device_id(
79
+ cuda_device_id,
80
+ verify_gpu=verify_gpu,
81
+ )
73
82
  self.save_gpu_memory = save_gpu_memory
74
83
  logger.debug("Launcher instance created.")
75
84
 
@@ -152,13 +161,20 @@ class Launcher:
152
161
  raise ValueError(message)
153
162
 
154
163
  @staticmethod
155
- def _configure_cuda_device_id(cuda_device_id: str | int | list | None) -> str:
164
+ def _configure_cuda_device_id(
165
+ cuda_device_id: str | int | list | None,
166
+ *,
167
+ verify_gpu: bool = True,
168
+ ) -> str:
156
169
  """Verify and assign the CUDA device ID.
157
170
 
158
171
  Parameters
159
172
  ----------
160
173
  cuda_device_id : str | int | None
161
174
  The CUDA device ID to verify and assign.
175
+ verify_gpu : bool, optional
176
+ Whether to verify that the specified CUDA devices exist.
177
+ Defaults to True.
162
178
 
163
179
  Returns
164
180
  -------
@@ -167,7 +183,8 @@ class Launcher:
167
183
 
168
184
  """
169
185
  output = Launcher._parse_cuda_device_id(cuda_device_id)
170
- Launcher._verify_cuda_devices_exist(output)
186
+ if verify_gpu:
187
+ Launcher._verify_cuda_devices_exist(output)
171
188
  return output
172
189
 
173
190
  def run(
@@ -5,13 +5,11 @@ import logging
5
5
  from collections import OrderedDict
6
6
  from dataclasses import dataclass, field
7
7
  from functools import cached_property
8
- from itertools import starmap
9
8
  from pathlib import Path
10
9
 
11
10
  import matplotlib.pyplot as plt
12
11
  import numexpr as ne
13
12
  import numpy as np
14
- from joblib import Parallel, delayed
15
13
  from numpy.typing import NDArray
16
14
 
17
15
  import fullwave
@@ -834,21 +832,24 @@ class PMLBuilder:
834
832
 
835
833
  items = list(rename_dict.items())
836
834
 
837
- results = Parallel(n_jobs=self.medium_org.n_jobs, backend="threading")(
838
- delayed(_compute_one)(
839
- key_fw2,
840
- key_py,
841
- relaxation_param_dict,
842
- alpha_target_higher_nu,
843
- d_target_higher_nu,
844
- alpha_target_pml,
845
- d_target_pml,
846
- n_polynomial,
847
- self.is_3d,
848
- self._apply_transition_and_pml,
849
- )
850
- for key_fw2, key_py in items
851
- )
835
+ with concurrent.futures.ThreadPoolExecutor() as executor:
836
+ futures = [
837
+ executor.submit(
838
+ _compute_one,
839
+ key_fw2,
840
+ key_py,
841
+ relaxation_param_dict,
842
+ alpha_target_higher_nu,
843
+ d_target_higher_nu,
844
+ alpha_target_pml,
845
+ d_target_pml,
846
+ n_polynomial,
847
+ self.is_3d,
848
+ self._apply_transition_and_pml,
849
+ )
850
+ for key_fw2, key_py in items
851
+ ]
852
+ results = [f.result() for f in futures]
852
853
  out_dict = dict(results)
853
854
 
854
855
  logger.debug("Calculating PML a and b coefficients...")
@@ -876,13 +877,9 @@ class PMLBuilder:
876
877
  # Return keys + values so parent can update dict safely
877
878
  return (f"a_pml_{axis}{nu}", a, f"b_pml_{axis}{nu}", b)
878
879
 
879
- results = Parallel(
880
- n_jobs=self.medium_org.n_jobs, # use all cores
881
- backend="loky", # process-based; safe default for Python code
882
- prefer="processes",
883
- )(
884
- starmap(delayed(_worker), tasks),
885
- )
880
+ with concurrent.futures.ThreadPoolExecutor() as executor:
881
+ futures = [executor.submit(_worker, nu, axis) for nu, axis in tasks]
882
+ results = [f.result() for f in futures]
886
883
 
887
884
  for a_key, a_val, b_key, b_val in results:
888
885
  out_dict[a_key] = a_val
@@ -1119,21 +1116,24 @@ class PMLBuilder:
1119
1116
  raise ValueError(error_msg)
1120
1117
 
1121
1118
  items = list(rename_dict.items())
1122
- results = Parallel(n_jobs=self.medium_org.n_jobs, backend="threading")(
1123
- delayed(_compute_one)(
1124
- key_fw2,
1125
- key_py,
1126
- relaxation_param_dict,
1127
- alpha_target_higher_nu,
1128
- d_target_higher_nu,
1129
- alpha_target_pml,
1130
- d_target_pml,
1131
- n_polynomial,
1132
- self.is_3d,
1133
- self._apply_transition_and_pml,
1134
- )
1135
- for key_fw2, key_py in items
1136
- )
1119
+ with concurrent.futures.ThreadPoolExecutor() as executor:
1120
+ futures = [
1121
+ executor.submit(
1122
+ _compute_one,
1123
+ key_fw2,
1124
+ key_py,
1125
+ relaxation_param_dict,
1126
+ alpha_target_higher_nu,
1127
+ d_target_higher_nu,
1128
+ alpha_target_pml,
1129
+ d_target_pml,
1130
+ n_polynomial,
1131
+ self.is_3d,
1132
+ self._apply_transition_and_pml,
1133
+ )
1134
+ for key_fw2, key_py in items
1135
+ ]
1136
+ results = [f.result() for f in futures]
1137
1137
  out_dict = dict(results)
1138
1138
 
1139
1139
  logger.debug("Calculating PML a and b coefficients...")
@@ -1162,13 +1162,9 @@ class PMLBuilder:
1162
1162
  # Return keys + values so parent can update dict safely
1163
1163
  return (f"a_pml_{axis}{nu}", a, f"b_pml_{axis}{nu}", b)
1164
1164
 
1165
- results = Parallel(
1166
- n_jobs=self.medium_org.n_jobs, # use all cores
1167
- backend="loky", # process-based; safe default for Python code
1168
- prefer="processes",
1169
- )(
1170
- starmap(delayed(_worker), tasks),
1171
- )
1165
+ with concurrent.futures.ThreadPoolExecutor() as executor:
1166
+ futures = [executor.submit(_worker, nu, axis) for nu, axis in tasks]
1167
+ results = [f.result() for f in futures]
1172
1168
 
1173
1169
  for a_key, a_val, b_key, b_val in results:
1174
1170
  out_dict[a_key] = a_val
@@ -329,6 +329,7 @@ class Solver:
329
329
  use_isotropic_relaxation: bool = True,
330
330
  cuda_device_id: str | int | list | None = None,
331
331
  save_gpu_memory: bool = False,
332
+ verify_gpu: bool = True,
332
333
  ) -> None:
333
334
  """Initialize a Solver instance for the fullwave simulation.
334
335
 
@@ -415,6 +416,11 @@ class Solver:
415
416
  depending on the hardware and the simulation settings.
416
417
  useful in 3D simulations with large grid sizes
417
418
  where GPU memory is a limiting factor.
419
+ verify_gpu : bool, optional
420
+ Whether to verify that the specified CUDA devices exist on the system.
421
+ Defaults to True. Set to False when generating input files only
422
+ (``generate_input_only=True``) on a machine that may not have
423
+ the target GPUs available.
418
424
 
419
425
  Raises
420
426
  ------
@@ -555,6 +561,7 @@ class Solver:
555
561
  use_gpu=self.use_gpu,
556
562
  cuda_device_id=self.cuda_device_id,
557
563
  save_gpu_memory=self.save_gpu_memory,
564
+ verify_gpu=verify_gpu,
558
565
  )
559
566
 
560
567
  if use_exponential_attenuation:
@@ -758,6 +765,7 @@ class Solver:
758
765
  release_after_write: bool = False,
759
766
  highpass_cutoff_mhz: float | None = None,
760
767
  bandpass_cutoff_mhz: tuple[float, float] | None = None,
768
+ gpu_memory_estimate: bool = True,
761
769
  ) -> NDArray[np.float64] | Path:
762
770
  r"""Run the fullwave simulation and return the result as a NumPy array.
763
771
 
@@ -829,6 +837,9 @@ class Solver:
829
837
  Uses cosine (Hann) tapers on both edges.
830
838
  Cannot be combined with ``highpass_cutoff_mhz``.
831
839
  Requires ``load_results=True``. Default is ``None`` (no filtering).
840
+ gpu_memory_estimate : bool
841
+ Whether to estimate GPU memory usage before running the simulation.
842
+ Default is True. If True, it estimates the GPU memory usage.
832
843
 
833
844
  Returns
834
845
  -------
@@ -937,6 +948,9 @@ class Solver:
937
948
  )
938
949
  logger.debug(message)
939
950
 
951
+ if gpu_memory_estimate:
952
+ self._estimate_gpu_memory(sensor)
953
+
940
954
  if generate_input_only:
941
955
  logger.info(
942
956
  "Input data generation completed in %s. Skipping simulation execution.",
@@ -975,6 +989,250 @@ class Solver:
975
989
  # which is a list of file names
976
990
  return sim_result
977
991
 
992
+ def _estimate_gpu_memory(
993
+ self,
994
+ sensor: fullwave.Sensor,
995
+ ) -> None:
996
+ """Estimate and log GPU memory usage per device.
997
+
998
+ Provides a pre-launch estimate so users can verify that the simulation
999
+ fits in GPU memory before the binary starts allocating.
1000
+
1001
+ Parameters
1002
+ ----------
1003
+ sensor : fullwave.Sensor
1004
+ The sensor that will actually be written to the input files (may
1005
+ differ from ``self.sensor`` when ``record_whole_domain=True``).
1006
+
1007
+ """
1008
+ # show that this is an experimental feature
1009
+ logger.info("Estimating GPU memory usage... (experimental feature, may be inaccurate)")
1010
+ device_ids = self.fullwave_launcher.cuda_device_id.split(",")
1011
+ n_gpus = len(device_ids)
1012
+
1013
+ grid = self.pml_builder.extended_grid
1014
+ source = self.pml_builder.extended_source
1015
+ medium = self.pml_builder.extended_medium
1016
+
1017
+ depth = grid.nx
1018
+ lateral = grid.ny * grid.nz if self.is_3d else grid.ny
1019
+ halo_depth = 8
1020
+
1021
+ float_bytes = 4
1022
+ int_bytes = 4
1023
+
1024
+ c_map = medium.sound_speed
1025
+ c_range = int(np.rint(c_map.max()) - np.rint(c_map.min()))
1026
+ n_deriv_levels = 1 if c_range == 0 else c_range + 1
1027
+
1028
+ n_source_timesteps = source.icmat.shape[1]
1029
+
1030
+ gb = 1024.0**3
1031
+
1032
+ base_depth = depth // n_gpus
1033
+ remainder = depth % n_gpus
1034
+
1035
+ for rank, dev_id in enumerate(device_ids):
1036
+ depth_this = base_depth + (1 if rank < remainder else 0)
1037
+
1038
+ if n_gpus == 1:
1039
+ n_halo_sides = 0
1040
+ elif rank == 0 or rank == n_gpus - 1:
1041
+ n_halo_sides = 1
1042
+ else:
1043
+ n_halo_sides = 2
1044
+ local_depth = depth_this + n_halo_sides * halo_depth
1045
+ slab = local_depth * lateral
1046
+
1047
+ n_sources = max(source.n_sources // n_gpus, 0)
1048
+ n_sensors = max(sensor.n_sensors // n_gpus, 0)
1049
+ n_air_local = max(medium.n_air // n_gpus, 0)
1050
+
1051
+ if self.use_exponential_attenuation:
1052
+ total = self._mem_exponential(
1053
+ slab,
1054
+ n_deriv_levels,
1055
+ n_sources,
1056
+ n_source_timesteps,
1057
+ save_gpu_memory=self.save_gpu_memory,
1058
+ n_sensors=n_sensors,
1059
+ float_bytes=float_bytes,
1060
+ int_bytes=int_bytes,
1061
+ is_3d=self.is_3d,
1062
+ )
1063
+ else:
1064
+ total = self._mem_relaxation(
1065
+ slab,
1066
+ n_deriv_levels,
1067
+ n_sources,
1068
+ n_source_timesteps,
1069
+ save_gpu_memory=self.save_gpu_memory,
1070
+ n_air=n_air_local,
1071
+ n_sensors=n_sensors,
1072
+ n_relax=self.n_relax_mechanisms,
1073
+ float_bytes=float_bytes,
1074
+ int_bytes=int_bytes,
1075
+ is_3d=self.is_3d,
1076
+ )
1077
+
1078
+ mode = "exponential" if self.use_exponential_attenuation else "relaxation"
1079
+ saving = ", save_gpu_memory=True" if self.save_gpu_memory else ""
1080
+ logger.info(
1081
+ "GPU memory estimate [GPU %s] (%s mode%s): "
1082
+ "%.2f GB (depth=%d +%d halo, lateral=%d)",
1083
+ dev_id.strip(),
1084
+ mode,
1085
+ saving,
1086
+ total / gb,
1087
+ depth_this,
1088
+ n_halo_sides * halo_depth,
1089
+ lateral,
1090
+ )
1091
+
1092
+ @staticmethod
1093
+ def _mem_exponential(
1094
+ slab: int,
1095
+ n_deriv_levels: int,
1096
+ n_sources: int,
1097
+ n_source_timesteps: int,
1098
+ *,
1099
+ save_gpu_memory: bool,
1100
+ n_sensors: int,
1101
+ float_bytes: int,
1102
+ int_bytes: int,
1103
+ is_3d: bool,
1104
+ ) -> int:
1105
+ """Return estimated GPU bytes for exponential-attenuation solver.
1106
+
1107
+ Parameters
1108
+ ----------
1109
+ slab : int
1110
+ Grid points per GPU slab (local_depth * lateral).
1111
+ n_deriv_levels : int
1112
+ Number of derivative-map levels.
1113
+ n_sources : int
1114
+ Approximate source count on this GPU.
1115
+ n_source_timesteps : int
1116
+ Number of source time steps.
1117
+ save_gpu_memory : bool
1118
+ Whether memory-saving mode is active.
1119
+ n_sensors : int
1120
+ Approximate sensor count on this GPU.
1121
+ float_bytes : int
1122
+ Bytes per float (4).
1123
+ int_bytes : int
1124
+ Bytes per int (4).
1125
+ is_3d: bool,
1126
+ Whether the simulation is 3D (affects sensor memory).
1127
+
1128
+ Returns
1129
+ -------
1130
+ int
1131
+ Total estimated bytes.
1132
+
1133
+ """
1134
+ fb = float_bytes
1135
+ ib = int_bytes
1136
+ ndim = 3 if is_3d else 2
1137
+ n_fields = 4 if is_3d else 3 # p, u, [v], w
1138
+
1139
+ # wave fields: n_fields pairs x 2 time levels
1140
+ mem = n_fields * 2 * slab * fb
1141
+ # material: rho + K + beta + a_exp
1142
+ mem += 4 * slab * fb
1143
+ # derivative maps (dmap + dcmap)
1144
+ mem += 9 * 2 * n_deriv_levels * fb + slab * ib
1145
+ # source (icmat + coords)
1146
+ if n_sources > 0:
1147
+ mem += n_sources * fb if save_gpu_memory else n_sources * n_source_timesteps * fb
1148
+ mem += ndim * n_sources * ib
1149
+ # sensor (genoutframe + coordsout_local + p_idx_array)
1150
+ if n_sensors > 0:
1151
+ mem += n_sensors * fb
1152
+ mem += (ndim + 1) * n_sensors * ib
1153
+ mem += n_sensors * ib
1154
+ return mem
1155
+
1156
+ @staticmethod
1157
+ def _mem_relaxation(
1158
+ slab: int,
1159
+ n_deriv_levels: int,
1160
+ n_sources: int,
1161
+ n_source_timesteps: int,
1162
+ *,
1163
+ save_gpu_memory: bool,
1164
+ n_air: int,
1165
+ n_sensors: int,
1166
+ n_relax: int,
1167
+ float_bytes: int,
1168
+ int_bytes: int,
1169
+ is_3d: bool,
1170
+ ) -> int:
1171
+ """Return estimated GPU bytes for relaxation (power-law) solver.
1172
+
1173
+ Parameters
1174
+ ----------
1175
+ slab : int
1176
+ Grid points per GPU slab (local_depth * lateral).
1177
+ n_deriv_levels : int
1178
+ Number of derivative-map levels.
1179
+ n_sources : int
1180
+ Approximate source count on this GPU.
1181
+ n_source_timesteps : int
1182
+ Number of source time steps.
1183
+ save_gpu_memory : bool
1184
+ Whether memory-saving mode is active.
1185
+ n_air : int
1186
+ Number of zero-pressure (air) coordinates on this GPU.
1187
+ n_sensors : int
1188
+ Approximate sensor count on this GPU.
1189
+ n_relax : int
1190
+ Number of relaxation mechanisms.
1191
+ float_bytes : int
1192
+ Bytes per float (4).
1193
+ int_bytes : int
1194
+ Bytes per int (4).
1195
+ is_3d: bool,
1196
+ Whether the simulation is 3D (affects sensor memory).
1197
+
1198
+ Returns
1199
+ -------
1200
+ int
1201
+ Total estimated bytes.
1202
+
1203
+ """
1204
+ fb = float_bytes
1205
+ ib = int_bytes
1206
+ ndim = 3 if is_3d else 2
1207
+ n_fields = 4 if is_3d else 3 # p, u, [v], w
1208
+
1209
+ # wave fields: n_fields pairs x 2 time levels
1210
+ mem = n_fields * 2 * slab * fb
1211
+ # relaxation psi:
1212
+ mem += 2 * (ndim * n_relax * 2 * slab * fb)
1213
+ # material: rho + K + beta
1214
+ mem += 3 * slab * fb
1215
+ # kappa: 2 arrays (kappa_x1, kappa_x2)
1216
+ mem += 2 * slab * fb
1217
+ # PML: pml_x1 + pml_x2, each has 2 * n_relax arrays
1218
+ mem += 2 * (2 * n_relax) * slab * fb
1219
+ # (dmap + dcmap)
1220
+ mem += 9 * 2 * n_deriv_levels * fb + slab * ib
1221
+ # source (icmat + coords)
1222
+ if n_sources > 0:
1223
+ mem += n_sources * fb if save_gpu_memory else n_sources * n_source_timesteps * fb
1224
+ mem += ndim * n_sources * ib
1225
+
1226
+ # air
1227
+ if n_air > 0:
1228
+ mem += ndim * n_air * ib
1229
+ # sensor
1230
+ if n_sensors > 0:
1231
+ mem += n_sensors * fb
1232
+ mem += (ndim + 1) * n_sensors * ib
1233
+ mem += n_sensors * ib
1234
+ return mem
1235
+
978
1236
  def print_info(self) -> None:
979
1237
  """Print the Solver instance information."""
980
1238
  print(str(self))
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fullwave25"
3
- version = "1.2.6-dev3" # Update via bump-my-version, not manually
3
+ version = "1.2.6-dev5" # 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"
@@ -12,7 +12,6 @@ dependencies = [
12
12
  "opencv-python-headless>=4.12.0.88",
13
13
  "tomli>=2.3.0",
14
14
  "numba>=0.63.1",
15
- "joblib>=1.5.3",
16
15
  "numexpr>=2.14.1",
17
16
  ]
18
17
  authors = [{ name = "Masashi Sode" }, { name = "Gianmarco Pinton" }]
@@ -57,4 +56,13 @@ build-backend = "hatchling.build"
57
56
  packages = ["fullwave", "fullwave.utils"]
58
57
 
59
58
  [tool.hatch.build]
60
- exclude = ["tests", "figs", "examples", ".vscode", ".github", "fullwave/solver/bins/gpu", "fullwave/solver/bins/exponential_attenuation", "fullwave/solver/bins/_exponential_attenuation"]
59
+ exclude = [
60
+ "tests",
61
+ "figs",
62
+ "examples",
63
+ ".vscode",
64
+ ".github",
65
+ "fullwave/solver/bins/gpu",
66
+ "fullwave/solver/bins/exponential_attenuation",
67
+ "fullwave/solver/bins/_exponential_attenuation",
68
+ ]
@@ -77,6 +77,7 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
77
77
  "tests/**" = [
78
78
  "D100",
79
79
  "D101",
80
+ "D102",
80
81
  "D103",
81
82
  "D104",
82
83
  "D107",
@@ -735,10 +735,9 @@ wheels = [
735
735
 
736
736
  [[package]]
737
737
  name = "fullwave25"
738
- version = "1.2.6.dev3"
738
+ version = "1.2.6.dev5"
739
739
  source = { editable = "." }
740
740
  dependencies = [
741
- { name = "joblib" },
742
741
  { name = "matplotlib" },
743
742
  { name = "numba" },
744
743
  { name = "numexpr" },
@@ -780,7 +779,6 @@ dev = [
780
779
  requires-dist = [
781
780
  { name = "cupy-cuda12x", marker = "extra == 'examples'", specifier = ">=13.6.0" },
782
781
  { name = "ipykernel", marker = "extra == 'examples'", specifier = ">=6.28.0" },
783
- { name = "joblib", specifier = ">=1.5.3" },
784
782
  { name = "jupyter", marker = "extra == 'examples'", specifier = ">=1.0.0" },
785
783
  { name = "line-profiler", marker = "extra == 'dev'", specifier = ">=5.0.1" },
786
784
  { name = "mach-beamform", marker = "extra == 'examples'", specifier = ">=0.0.4" },
@@ -1034,15 +1032,6 @@ wheels = [
1034
1032
  { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
1035
1033
  ]
1036
1034
 
1037
- [[package]]
1038
- name = "joblib"
1039
- version = "1.5.3"
1040
- source = { registry = "https://pypi.org/simple" }
1041
- sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" }
1042
- wheels = [
1043
- { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
1044
- ]
1045
-
1046
1035
  [[package]]
1047
1036
  name = "json5"
1048
1037
  version = "0.12.1"
File without changes