fullwave25 1.3.0.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.
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/.bumpversion.toml +1 -1
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/PKG-INFO +2 -1
- fullwave25-1.3.2/attenuation.png +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/__init__.py +14 -1
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium.py +29 -6
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/binary_manager.py +1 -1
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/input_file_writer.py +34 -17
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/pml_builder.py +94 -37
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/solver.py +259 -75
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/transmit.py +20 -4
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/utils/relaxation_parameters.py +12 -3
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/pyproject.toml +3 -1
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/uv.lock +157 -1
- fullwave25-1.3.0.dev0/CLAUDE.md +0 -286
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/.gitignore +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/.pre-commit-config.yaml +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/.python-version +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/CITATION.cff +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/CONTRIBUTING.md +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/LICENSE +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/Makefile +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/README.md +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/beamformer.m +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124 +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124 +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/experiments/__init__.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/experiments/exp_scatterer_diameter.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/beamformer/__init__.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/beamformer/beamformer.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/constants/__init__.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/constants/material_properties.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/grid.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/__init__.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/domain.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/medium_builder.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/__init__.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/data/.keep +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/data/abdominal_wall/i2365f_etfw1.mat +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/domain_abdominal_wall.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/domain_background.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/domain_scatterer.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/domain_simple.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/medium_builder/presets/domain_water_gel.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/sensor.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/__init__.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda118 +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda129 +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda130 +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda118 +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda124 +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda129 +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda130 +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/database/relaxation_params_database_num_relax=3_20260828_1513.mat +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/database/relaxation_params_database_num_relax=3_20260828_1513_invalid_cells.json +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/database/relaxation_params_database_num_relax=4_20260828_1456.mat +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/bins/database/relaxation_params_database_num_relax=4_20260828_1456_invalid_cells.json +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/cuda_utils.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/launcher.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/shipped_database.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/source_type.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/solver/utils.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/source.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/transducer.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/utils/__init__.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/utils/check_functions.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/utils/coordinates.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/utils/memory_tempfile.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/utils/numerical.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/utils/plot_utils.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/utils/pulse.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/utils/scatterer.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/utils/signal_filter.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/fullwave/utils/signal_process.py +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/ruff.toml +0 -0
- {fullwave25-1.3.0.dev0 → fullwave25-1.3.2}/signal_filter_example.png +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fullwave25
|
|
3
|
-
Version: 1.3.
|
|
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
|
|
@@ -13,6 +13,7 @@ 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: cupy-cuda12x>=13.6.0
|
|
16
17
|
Requires-Dist: matplotlib>=3.10.7
|
|
17
18
|
Requires-Dist: numba>=0.63.1
|
|
18
19
|
Requires-Dist: numexpr>=2.14.1
|
|
Binary file
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"""fullwave module."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import os
|
|
4
5
|
import platform
|
|
5
6
|
import time
|
|
6
7
|
from importlib.metadata import PackageNotFoundError, version
|
|
7
8
|
|
|
9
|
+
import numexpr
|
|
10
|
+
|
|
8
11
|
from .grid import Grid
|
|
9
12
|
from .medium import Medium, MediumExponentialAttenuation, MediumRelaxationMaps
|
|
10
13
|
from .sensor import Sensor
|
|
@@ -63,7 +66,17 @@ try:
|
|
|
63
66
|
__version__ = version("fullwave")
|
|
64
67
|
except PackageNotFoundError:
|
|
65
68
|
# Update via bump-my-version, not manually
|
|
66
|
-
__version__ = "1.3.
|
|
69
|
+
__version__ = "1.3.2"
|
|
67
70
|
|
|
68
71
|
VERSION = __version__ # for convenience
|
|
69
72
|
logger.info("Fullwave version: %s", __version__)
|
|
73
|
+
|
|
74
|
+
# numexpr keeps 16 threads unless told otherwise, and the medium, the absorbing
|
|
75
|
+
# layer and the coefficients run through it on grids of hundreds of millions of
|
|
76
|
+
# cells. A thread count set in the environment is left alone.
|
|
77
|
+
if not any(
|
|
78
|
+
os.environ.get(name)
|
|
79
|
+
for name in ("NUMEXPR_MAX_THREADS", "NUMEXPR_NUM_THREADS", "OMP_NUM_THREADS")
|
|
80
|
+
):
|
|
81
|
+
numexpr.set_num_threads(min(numexpr.detect_number_of_cores(), numexpr.MAX_THREADS))
|
|
82
|
+
logger.info("numexpr threads: %d", numexpr.get_num_threads())
|
|
@@ -19,6 +19,7 @@ from fullwave.solver.utils import initialize_relaxation_param_dict
|
|
|
19
19
|
from fullwave.utils import check_functions, plot_utils
|
|
20
20
|
from fullwave.utils.coordinates import coords_to_map, map_to_coords
|
|
21
21
|
from fullwave.utils.relaxation_parameters import (
|
|
22
|
+
_array_module,
|
|
22
23
|
band_scaled_sound_speed,
|
|
23
24
|
generate_relaxation_params,
|
|
24
25
|
)
|
|
@@ -200,7 +201,8 @@ def _make_the_lossless_voxels_lossless(
|
|
|
200
201
|
"""
|
|
201
202
|
if not relaxation_param_dict:
|
|
202
203
|
return
|
|
203
|
-
|
|
204
|
+
xp = np if np.ndim(alpha_coeff) == 0 else _array_module(alpha_coeff)
|
|
205
|
+
coefficient = xp.asarray(alpha_coeff)
|
|
204
206
|
if coefficient.ndim == 0:
|
|
205
207
|
if float(coefficient) != 0.0:
|
|
206
208
|
return
|
|
@@ -273,6 +275,7 @@ class MediumRelaxationMaps:
|
|
|
273
275
|
n_jobs: int = -1,
|
|
274
276
|
dtype: type = np.float64,
|
|
275
277
|
use_gpu: bool = False,
|
|
278
|
+
compute_coefficients: bool = True,
|
|
276
279
|
) -> None:
|
|
277
280
|
"""Medium class for Fullwave.
|
|
278
281
|
|
|
@@ -330,6 +333,10 @@ class MediumRelaxationMaps:
|
|
|
330
333
|
use_gpu : bool, optional
|
|
331
334
|
If True, use CuPy for GPU-accelerated computation (default is False).
|
|
332
335
|
Requires CuPy to be installed. Falls back to CPU if CuPy is unavailable.
|
|
336
|
+
compute_coefficients : bool, optional
|
|
337
|
+
If True (the default), compute ``relaxation_param_dict_for_fw2``
|
|
338
|
+
here. False leaves it empty for a caller that writes every entry
|
|
339
|
+
itself, as the PML builder does.
|
|
333
340
|
|
|
334
341
|
"""
|
|
335
342
|
check_functions.check_compatible_value(
|
|
@@ -354,7 +361,6 @@ class MediumRelaxationMaps:
|
|
|
354
361
|
try:
|
|
355
362
|
self.relaxation_param_dict = initialize_relaxation_param_dict(
|
|
356
363
|
n_relaxation_mechanisms=n_relaxation_mechanisms,
|
|
357
|
-
value=xp.zeros_like(xp.asarray(sound_speed), dtype=self.dtype),
|
|
358
364
|
)
|
|
359
365
|
self.grid = grid
|
|
360
366
|
self.is_3d = grid.is_3d
|
|
@@ -379,7 +385,6 @@ class MediumRelaxationMaps:
|
|
|
379
385
|
xp = np
|
|
380
386
|
self.relaxation_param_dict = initialize_relaxation_param_dict(
|
|
381
387
|
n_relaxation_mechanisms=n_relaxation_mechanisms,
|
|
382
|
-
value=np.zeros_like(np.asarray(sound_speed), dtype=self.dtype),
|
|
383
388
|
)
|
|
384
389
|
self.grid = grid
|
|
385
390
|
self.is_3d = grid.is_3d
|
|
@@ -404,12 +409,23 @@ class MediumRelaxationMaps:
|
|
|
404
409
|
relaxation_param_updates=relaxation_param_dict,
|
|
405
410
|
)
|
|
406
411
|
self.use_isotropic_relaxation = use_isotropic_relaxation
|
|
407
|
-
self.relaxation_param_dict_for_fw2 =
|
|
408
|
-
|
|
412
|
+
self.relaxation_param_dict_for_fw2 = (
|
|
413
|
+
self._calc_relaxation_param_dict_for_fw2(
|
|
414
|
+
use_isotropic_relaxation=self.use_isotropic_relaxation,
|
|
415
|
+
)
|
|
416
|
+
if compute_coefficients
|
|
417
|
+
else {}
|
|
409
418
|
)
|
|
410
419
|
self.check_fields()
|
|
411
420
|
logger.debug("MediumRelaxationMaps instance created.")
|
|
412
421
|
|
|
422
|
+
def build_coefficients(self) -> None:
|
|
423
|
+
"""Compute ``relaxation_param_dict_for_fw2`` if the constructor was told to skip it."""
|
|
424
|
+
if not self.relaxation_param_dict_for_fw2:
|
|
425
|
+
self.relaxation_param_dict_for_fw2 = self._calc_relaxation_param_dict_for_fw2(
|
|
426
|
+
use_isotropic_relaxation=self.use_isotropic_relaxation,
|
|
427
|
+
)
|
|
428
|
+
|
|
413
429
|
def _update_relaxation_param_dict(
|
|
414
430
|
self,
|
|
415
431
|
relaxation_param_updates: dict[str, NDArray[np.float64]],
|
|
@@ -1627,12 +1643,18 @@ class Medium:
|
|
|
1627
1643
|
parameters = self._relaxation_parameters_of(sound_speed, alpha_coeff, alpha_power)
|
|
1628
1644
|
return parameters, sound_speed
|
|
1629
1645
|
|
|
1630
|
-
def build(self) -> MediumRelaxationMaps:
|
|
1646
|
+
def build(self, *, compute_coefficients: bool = True) -> MediumRelaxationMaps:
|
|
1631
1647
|
"""Retrieve the relaxation parameters from alpha and power maps.
|
|
1632
1648
|
|
|
1633
1649
|
it uses the relaxation parameters look up table
|
|
1634
1650
|
to generate the relaxation parameters.
|
|
1635
1651
|
|
|
1652
|
+
Parameters
|
|
1653
|
+
----------
|
|
1654
|
+
compute_coefficients : bool, optional
|
|
1655
|
+
Passed to `MediumRelaxationMaps`. False skips the solver
|
|
1656
|
+
coefficients, which a caller that extends the maps first does not use.
|
|
1657
|
+
|
|
1636
1658
|
Returns
|
|
1637
1659
|
-------
|
|
1638
1660
|
MediumRelaxationMaps: An instance of MediumRelaxationMaps
|
|
@@ -1670,6 +1692,7 @@ class Medium:
|
|
|
1670
1692
|
n_jobs=self.n_jobs,
|
|
1671
1693
|
dtype=self.dtype,
|
|
1672
1694
|
use_gpu=self.use_gpu,
|
|
1695
|
+
compute_coefficients=compute_coefficients,
|
|
1673
1696
|
)
|
|
1674
1697
|
|
|
1675
1698
|
def _db_mhz_cm_to_a_exp(
|
|
@@ -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.
|
|
31
|
+
BINARY_RELEASE_TAG = "fullwave_bin_v1.4.4"
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def _download_url(filename: str, tag: str) -> str:
|
|
@@ -56,7 +56,7 @@ class InputFileWriter:
|
|
|
56
56
|
path_fullwave_simulation_bin: Path = Path(__file__).parent / "bins" / "fullwave_solver_gpu",
|
|
57
57
|
validate_input: bool = True,
|
|
58
58
|
use_exponential_attenuation: bool = False,
|
|
59
|
-
use_isotropic_relaxation: bool =
|
|
59
|
+
use_isotropic_relaxation: bool = True,
|
|
60
60
|
release_after_write: bool = False,
|
|
61
61
|
pml_thickness: int = 0,
|
|
62
62
|
exponential_attenuation_pml_thickness_px: int = 0,
|
|
@@ -94,6 +94,7 @@ class InputFileWriter:
|
|
|
94
94
|
to reduce memory usage while retaining accuracy.
|
|
95
95
|
For 2D it will reduce the memory usage by approximately 15%.
|
|
96
96
|
For 3D it will reduce the memory usage by approximately 25%.
|
|
97
|
+
Only True is supported, and False raises NotImplementedError.
|
|
97
98
|
This option omits the anisotropic relaxation mechanisms to model the attenuation.
|
|
98
99
|
We usually recommend using isotropic relaxation mechanisms
|
|
99
100
|
unless the anisotropic attenuation is required for the simulation.
|
|
@@ -126,6 +127,12 @@ class InputFileWriter:
|
|
|
126
127
|
"""
|
|
127
128
|
logger.debug("Initializing InputFileWriter instance.")
|
|
128
129
|
|
|
130
|
+
if use_isotropic_relaxation is False:
|
|
131
|
+
error_msg = (
|
|
132
|
+
"Currently, only isotropic relaxation is supported. "
|
|
133
|
+
"Please set use_isotropic_relaxation to True for the simulation."
|
|
134
|
+
)
|
|
135
|
+
raise NotImplementedError(error_msg)
|
|
129
136
|
self._work_dir = Path(work_dir)
|
|
130
137
|
self.path_fullwave_simulation_bin = path_fullwave_simulation_bin
|
|
131
138
|
self.use_isotropic_relaxation = use_isotropic_relaxation
|
|
@@ -1228,16 +1235,8 @@ class InputFileWriter:
|
|
|
1228
1235
|
mechanisms = range(1, self.medium.n_relaxation_mechanisms + 1)
|
|
1229
1236
|
for nu in mechanisms:
|
|
1230
1237
|
var_name_list.extend([f"apmlu{nu}", f"bpmlu{nu}", f"apmlx{nu}", f"bpmlx{nu}"])
|
|
1231
|
-
if not self.use_isotropic_relaxation:
|
|
1232
|
-
var_name_list.extend(["kappay", "kappaw"])
|
|
1233
|
-
for nu in mechanisms:
|
|
1234
|
-
var_name_list.extend([f"apmlw{nu}", f"apmly{nu}", f"bpmlw{nu}", f"bpmly{nu}"])
|
|
1235
1238
|
if self.is_3d:
|
|
1236
|
-
var_name_list.
|
|
1237
|
-
if self.is_3d and not self.use_isotropic_relaxation:
|
|
1238
|
-
var_name_list.extend(["nZ", "dZ", "kappaz", "kappav"])
|
|
1239
|
-
for nu in mechanisms:
|
|
1240
|
-
var_name_list.extend([f"apmlz{nu}", f"apmlv{nu}", f"bpmlz{nu}", f"bpmlv{nu}"])
|
|
1239
|
+
var_name_list.extend(["modZ", "nZ", "dZ"])
|
|
1241
1240
|
for var_name in var_name_list:
|
|
1242
1241
|
src_data = src_dir / f"{var_name}.dat"
|
|
1243
1242
|
dst_data = dst_dir / f"{var_name}.dat"
|
|
@@ -1409,6 +1408,29 @@ class InputFileWriter:
|
|
|
1409
1408
|
dst.unlink()
|
|
1410
1409
|
dst.symlink_to(self.path_fullwave_simulation_bin.resolve())
|
|
1411
1410
|
|
|
1411
|
+
@staticmethod
|
|
1412
|
+
def _write_in_slabs(
|
|
1413
|
+
variable_mat: np.ndarray,
|
|
1414
|
+
dtype: DTypeLike,
|
|
1415
|
+
save_path: str | Path,
|
|
1416
|
+
slab_bytes: int = 256 * 1024**2,
|
|
1417
|
+
) -> None:
|
|
1418
|
+
"""Write an array in C order, cast one slab of its first axis at a time.
|
|
1419
|
+
|
|
1420
|
+
The bytes are those of one whole cast buffer, and the largest buffer
|
|
1421
|
+
held is one slab, so many maps can be written at once.
|
|
1422
|
+
"""
|
|
1423
|
+
dtype = np.dtype(dtype)
|
|
1424
|
+
if variable_mat.ndim == 0:
|
|
1425
|
+
np.ascontiguousarray(variable_mat, dtype=dtype).tofile(save_path)
|
|
1426
|
+
return
|
|
1427
|
+
row_bytes = max(1, dtype.itemsize * (variable_mat.size // max(variable_mat.shape[0], 1)))
|
|
1428
|
+
rows_per_slab = max(1, slab_bytes // row_bytes)
|
|
1429
|
+
with Path(save_path).open("wb") as handle:
|
|
1430
|
+
for start in range(0, variable_mat.shape[0], rows_per_slab):
|
|
1431
|
+
slab = variable_mat[start : start + rows_per_slab]
|
|
1432
|
+
np.ascontiguousarray(slab, dtype=dtype).tofile(handle)
|
|
1433
|
+
|
|
1412
1434
|
@staticmethod
|
|
1413
1435
|
def _write_ic(
|
|
1414
1436
|
fname: str | Path,
|
|
@@ -1418,11 +1440,7 @@ class InputFileWriter:
|
|
|
1418
1440
|
logger.debug("Writing initial condition matrix to %s", fname, stacklevel=2)
|
|
1419
1441
|
|
|
1420
1442
|
t0 = time.perf_counter()
|
|
1421
|
-
|
|
1422
|
-
icmat.T,
|
|
1423
|
-
dtype=np.float32,
|
|
1424
|
-
) # does transpose+cast into one new buffer
|
|
1425
|
-
out.tofile(fname)
|
|
1443
|
+
InputFileWriter._write_in_slabs(icmat.T, np.float32, fname)
|
|
1426
1444
|
t1 = time.perf_counter()
|
|
1427
1445
|
|
|
1428
1446
|
logger.debug("Initial condition matrix written in %.2e seconds", t1 - t0, stacklevel=2)
|
|
@@ -1486,8 +1504,7 @@ class InputFileWriter:
|
|
|
1486
1504
|
if variable_mat.dtype == dtype and variable_mat.flags.c_contiguous:
|
|
1487
1505
|
variable_mat.tofile(save_path) # writes in C order
|
|
1488
1506
|
else:
|
|
1489
|
-
|
|
1490
|
-
out.tofile(save_path)
|
|
1507
|
+
InputFileWriter._write_in_slabs(variable_mat, dtype, save_path)
|
|
1491
1508
|
|
|
1492
1509
|
t1 = time.perf_counter()
|
|
1493
1510
|
logger.debug("Matrix written in %.2e seconds", t1 - t0, stacklevel=2)
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import concurrent.futures
|
|
6
6
|
import gc
|
|
7
7
|
import logging
|
|
8
|
+
import os
|
|
8
9
|
from collections import OrderedDict
|
|
9
10
|
from dataclasses import dataclass, field
|
|
10
11
|
from functools import cached_property
|
|
@@ -312,12 +313,20 @@ class PMLBuilder:
|
|
|
312
313
|
logger.debug("building extended medium for pml...")
|
|
313
314
|
medium_as_given = self.medium_org
|
|
314
315
|
if not isinstance(self.medium_org, fullwave.MediumRelaxationMaps):
|
|
315
|
-
|
|
316
|
+
logger.info(
|
|
317
|
+
"Building the relaxation maps of %d cells...", int(self.medium_org.sound_speed.size)
|
|
318
|
+
)
|
|
319
|
+
self.medium_org = self.medium_org.build(compute_coefficients=False)
|
|
316
320
|
base_attrs = ["sound_speed", "density", "beta"]
|
|
317
321
|
original_alpha_coeff = getattr(self.medium_org, "alpha_coeff", None)
|
|
318
322
|
if original_alpha_coeff is not None and np.ndim(original_alpha_coeff) != 0:
|
|
319
323
|
base_attrs.append("alpha_coeff")
|
|
320
324
|
relax_attrs = list(self.medium_org.relaxation_param_dict.keys())
|
|
325
|
+
logger.info(
|
|
326
|
+
"Extending %d maps by %d cells on each side...",
|
|
327
|
+
len(base_attrs) + len(relax_attrs),
|
|
328
|
+
self.num_boundary_points,
|
|
329
|
+
)
|
|
321
330
|
|
|
322
331
|
if self.xp is not np:
|
|
323
332
|
# Pass CuPy arrays directly — multi-GPU extension uses D2D copy (NVLink)
|
|
@@ -366,7 +375,11 @@ class PMLBuilder:
|
|
|
366
375
|
n_jobs=self.medium_org.n_jobs,
|
|
367
376
|
dtype=getattr(self.medium_org, "dtype", np.float64),
|
|
368
377
|
use_gpu=False,
|
|
378
|
+
compute_coefficients=False,
|
|
369
379
|
)
|
|
380
|
+
# The extended maps carry everything the absorbing layer reads, so the
|
|
381
|
+
# interior maps are released here rather than held through the run.
|
|
382
|
+
self.medium_org = medium_as_given
|
|
370
383
|
logger.debug("building extended medium for pml...done")
|
|
371
384
|
|
|
372
385
|
logger.debug("building extended source for pml...")
|
|
@@ -861,10 +874,16 @@ class PMLBuilder:
|
|
|
861
874
|
|
|
862
875
|
eps = xp.finfo(xp.float64).eps
|
|
863
876
|
|
|
864
|
-
rate = d_x / kappa_x + alpha_x
|
|
865
877
|
two_over_dt = 2.0 / dt
|
|
866
|
-
|
|
867
|
-
|
|
878
|
+
if xp is np:
|
|
879
|
+
rate = ne.evaluate("d_x / kappa_x + alpha_x")
|
|
880
|
+
b = ne.evaluate("(two_over_dt - rate) / (two_over_dt + rate)")
|
|
881
|
+
a = ne.evaluate("-(d_x / (kappa_x ** 2 + eps)) / (rate + two_over_dt)")
|
|
882
|
+
else:
|
|
883
|
+
rate = d_x / kappa_x + alpha_x
|
|
884
|
+
b = (two_over_dt - rate) / (two_over_dt + rate)
|
|
885
|
+
a = -(d_x / (kappa_x**2 + eps)) / (rate + two_over_dt)
|
|
886
|
+
del rate
|
|
868
887
|
|
|
869
888
|
if output_dtype is not None and output_dtype != xp.float64:
|
|
870
889
|
a = a.astype(output_dtype, copy=False)
|
|
@@ -886,6 +905,10 @@ class PMLBuilder:
|
|
|
886
905
|
"""
|
|
887
906
|
logger.debug("Running PML builder...")
|
|
888
907
|
if use_pml:
|
|
908
|
+
logger.info(
|
|
909
|
+
"Building the absorbing layer on %d cells...",
|
|
910
|
+
int(self.extended_medium.sound_speed.size),
|
|
911
|
+
)
|
|
889
912
|
extended_medium: fullwave.MediumRelaxationMaps = self.extended_medium.build()
|
|
890
913
|
if self.is_3d:
|
|
891
914
|
return self._apply_pml_3d(
|
|
@@ -901,30 +924,54 @@ class PMLBuilder:
|
|
|
901
924
|
)
|
|
902
925
|
|
|
903
926
|
extended_medium: fullwave.MediumRelaxationMaps = self.extended_medium.build()
|
|
927
|
+
extended_medium.build_coefficients()
|
|
904
928
|
return extended_medium
|
|
905
929
|
|
|
906
930
|
def _medium_copy(self, relaxation_param_dict: dict, key: str) -> NDArray[np.float64]:
|
|
907
|
-
"""Return a fresh float64 copy of one medium map.
|
|
908
|
-
|
|
931
|
+
"""Return a fresh float64 copy of one medium map.
|
|
932
|
+
|
|
933
|
+
On the host the copy is split along the first axis across threads,
|
|
934
|
+
because one thread copying a map of the whole padded grid is the slowest
|
|
935
|
+
step of the absorbing layer.
|
|
936
|
+
"""
|
|
937
|
+
source = relaxation_param_dict[key]
|
|
938
|
+
if self.xp is not np or np.ndim(source) == 0:
|
|
939
|
+
return self.xp.array(source, dtype=self.xp.float64, copy=True)
|
|
940
|
+
copied = np.empty(np.shape(source), dtype=np.float64)
|
|
941
|
+
n_rows = copied.shape[0]
|
|
942
|
+
n_slabs = max(1, min(n_rows, os.cpu_count() or 1, ne.MAX_THREADS))
|
|
943
|
+
edges = np.linspace(0, n_rows, n_slabs + 1).astype(int)
|
|
944
|
+
|
|
945
|
+
def copy_slab(start: int, stop: int) -> None:
|
|
946
|
+
np.copyto(copied[start:stop], source[start:stop], casting="unsafe")
|
|
947
|
+
|
|
948
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=n_slabs) as pool:
|
|
949
|
+
list(pool.map(copy_slab, edges[:-1], edges[1:]))
|
|
950
|
+
return copied
|
|
909
951
|
|
|
910
952
|
def _ramp_on_every_axis(
|
|
911
|
-
self, field: NDArray[np.float64], **kwargs: object
|
|
953
|
+
self, field: NDArray[np.float64], *, in_place: bool = False, **kwargs: object
|
|
912
954
|
) -> NDArray[np.float64]:
|
|
913
|
-
"""Apply one ramp along each grid axis in turn, in two dimensions or three.
|
|
955
|
+
"""Apply one ramp along each grid axis in turn, in two dimensions or three.
|
|
956
|
+
|
|
957
|
+
The ramp writes only the layers at both ends of each axis. With
|
|
958
|
+
`in_place` the field is written itself, otherwise one copy is written.
|
|
959
|
+
"""
|
|
914
960
|
is_3d = field.ndim == self.three_dimensions
|
|
915
|
-
out = field
|
|
961
|
+
out = field if in_place else self.xp.array(field, copy=True)
|
|
916
962
|
for axis_index in range(field.ndim):
|
|
917
963
|
out = self._apply_transition_and_pml(
|
|
918
|
-
out, array_shape=field.shape, axis=axis_index, is_3d=is_3d, **kwargs
|
|
964
|
+
out, array_shape=field.shape, axis=axis_index, is_3d=is_3d, in_place=True, **kwargs
|
|
919
965
|
)
|
|
920
966
|
return out
|
|
921
967
|
|
|
922
968
|
def _empty_damping_in_transition_layer(
|
|
923
|
-
self, damping: NDArray[np.float64]
|
|
969
|
+
self, damping: NDArray[np.float64], *, in_place: bool = False
|
|
924
970
|
) -> NDArray[np.float64]:
|
|
925
971
|
"""Return the damping strength taken to zero across the transition layer."""
|
|
926
972
|
return self._ramp_on_every_axis(
|
|
927
973
|
damping,
|
|
974
|
+
in_place=in_place,
|
|
928
975
|
value_target=0.0,
|
|
929
976
|
transition_type="cosine",
|
|
930
977
|
transit_within_transition_layer=True,
|
|
@@ -935,10 +982,13 @@ class PMLBuilder:
|
|
|
935
982
|
damping: NDArray[np.float64],
|
|
936
983
|
n_polynomial: float,
|
|
937
984
|
d_target_pml: float,
|
|
985
|
+
*,
|
|
986
|
+
in_place: bool = False,
|
|
938
987
|
) -> NDArray[np.float64]:
|
|
939
988
|
"""Return the damping strength raised to the PML target across the PML layer."""
|
|
940
989
|
return self._ramp_on_every_axis(
|
|
941
990
|
damping,
|
|
991
|
+
in_place=in_place,
|
|
942
992
|
value_target=d_target_pml,
|
|
943
993
|
n_polynomial=n_polynomial,
|
|
944
994
|
transition_type="polynomial",
|
|
@@ -949,27 +999,36 @@ class PMLBuilder:
|
|
|
949
999
|
self,
|
|
950
1000
|
relaxation_frequency: NDArray[np.float64],
|
|
951
1001
|
entrance: float,
|
|
1002
|
+
*,
|
|
1003
|
+
in_place: bool = False,
|
|
952
1004
|
) -> NDArray[np.float64]:
|
|
953
1005
|
"""Return the relaxation frequency replaced by the CFS ramp inside the PML layer.
|
|
954
1006
|
|
|
955
1007
|
Replaced rather than ramped into, because ramping a relaxation frequency
|
|
956
1008
|
through the evaluation frequency is what confines the damping to the
|
|
957
|
-
outer edge.
|
|
1009
|
+
outer edge. The PML layer is the slab of `edge` cells at both ends of
|
|
1010
|
+
each axis, so only those slabs are copied from the ramp.
|
|
958
1011
|
"""
|
|
959
1012
|
xp = self.xp
|
|
960
1013
|
edge = self.m_spatial_order + self.n_pml_layer
|
|
961
1014
|
ramp = self._ramp_on_every_axis(
|
|
962
1015
|
xp.full_like(relaxation_frequency, entrance),
|
|
1016
|
+
in_place=True,
|
|
963
1017
|
value_target=0.0,
|
|
964
1018
|
transition_type="linear",
|
|
965
1019
|
transit_within_pml_layer=True,
|
|
966
1020
|
)
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1021
|
+
spliced = (
|
|
1022
|
+
xp.asarray(relaxation_frequency)
|
|
1023
|
+
if in_place
|
|
1024
|
+
else xp.array(relaxation_frequency, copy=True)
|
|
1025
|
+
)
|
|
1026
|
+
for axis_index in range(spliced.ndim):
|
|
1027
|
+
target = xp.moveaxis(spliced, axis_index, 0)
|
|
1028
|
+
source = xp.moveaxis(ramp, axis_index, 0)
|
|
1029
|
+
target[:edge] = source[:edge]
|
|
1030
|
+
target[target.shape[0] - edge :] = source[source.shape[0] - edge :]
|
|
1031
|
+
return spliced
|
|
973
1032
|
|
|
974
1033
|
def _build_decoupled(
|
|
975
1034
|
self,
|
|
@@ -1002,16 +1061,16 @@ class PMLBuilder:
|
|
|
1002
1061
|
relaxation_frequency = self._medium_copy(
|
|
1003
1062
|
relaxation_param_dict, rename_dict[f"alpha_{letter}_nu{nu}"]
|
|
1004
1063
|
)
|
|
1005
|
-
emptied = self._empty_damping_in_transition_layer(damping)
|
|
1064
|
+
emptied = self._empty_damping_in_transition_layer(damping, in_place=True)
|
|
1006
1065
|
if nu > 1:
|
|
1007
1066
|
out_dict[f"d_{letter}_nu{nu}"] = emptied
|
|
1008
1067
|
out_dict[f"alpha_{letter}_nu{nu}"] = relaxation_frequency
|
|
1009
1068
|
continue
|
|
1010
1069
|
out_dict[f"d_{letter}_nu{nu}"] = self._raise_damping_in_pml_layer(
|
|
1011
|
-
emptied, n_polynomial, d_target_pml
|
|
1070
|
+
emptied, n_polynomial, d_target_pml, in_place=True
|
|
1012
1071
|
)
|
|
1013
1072
|
out_dict[f"alpha_{letter}_nu{nu}"] = self._splice_entrance_frequency_into_pml_layer(
|
|
1014
|
-
relaxation_frequency, entrance
|
|
1073
|
+
relaxation_frequency, entrance, in_place=True
|
|
1015
1074
|
)
|
|
1016
1075
|
return out_dict
|
|
1017
1076
|
|
|
@@ -1124,9 +1183,11 @@ class PMLBuilder:
|
|
|
1124
1183
|
found = np.asarray(relaxation_param_dict[key], dtype=np.float64)
|
|
1125
1184
|
if found.ndim == 0 or np.allclose(found, 1.0):
|
|
1126
1185
|
continue
|
|
1186
|
+
found = self.xp.array(found, copy=True)
|
|
1127
1187
|
for axis in range(3 if is_3d else 2):
|
|
1128
1188
|
found = self._apply_transition_and_pml(
|
|
1129
1189
|
found,
|
|
1190
|
+
in_place=True,
|
|
1130
1191
|
value_target=1.0,
|
|
1131
1192
|
array_shape=found.shape,
|
|
1132
1193
|
axis=axis,
|
|
@@ -1213,13 +1274,9 @@ class PMLBuilder:
|
|
|
1213
1274
|
# Return keys + values so parent can update dict safely
|
|
1214
1275
|
return (f"a_pml_{axis}{nu}", a, f"b_pml_{axis}{nu}", b)
|
|
1215
1276
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
else:
|
|
1220
|
-
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
1221
|
-
futures = [executor.submit(_worker, nu, axis) for nu, axis in tasks]
|
|
1222
|
-
results = [f.result() for f in futures]
|
|
1277
|
+
# One task at a time. numexpr threads each expression, and a pool of
|
|
1278
|
+
# tasks held every task's temporaries of the whole grid at once.
|
|
1279
|
+
results = [_worker(nu, axis) for nu, axis in tasks]
|
|
1223
1280
|
|
|
1224
1281
|
for a_key, a_val, b_key, b_val in results:
|
|
1225
1282
|
out_dict[a_key] = a_val
|
|
@@ -1312,13 +1369,9 @@ class PMLBuilder:
|
|
|
1312
1369
|
# Return keys + values so parent can update dict safely
|
|
1313
1370
|
return (f"a_pml_{axis}{nu}", a, f"b_pml_{axis}{nu}", b)
|
|
1314
1371
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
else:
|
|
1319
|
-
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
1320
|
-
futures = [executor.submit(_worker, nu, axis) for nu, axis in tasks]
|
|
1321
|
-
results = [f.result() for f in futures]
|
|
1372
|
+
# One task at a time. numexpr threads each expression, and a pool of
|
|
1373
|
+
# tasks held every task's temporaries of the whole grid at once.
|
|
1374
|
+
results = [_worker(nu, axis) for nu, axis in tasks]
|
|
1322
1375
|
|
|
1323
1376
|
for a_key, a_val, b_key, b_val in results:
|
|
1324
1377
|
out_dict[a_key] = a_val
|
|
@@ -1347,6 +1400,7 @@ class PMLBuilder:
|
|
|
1347
1400
|
transit_within_pml_layer: bool = False,
|
|
1348
1401
|
disable_the_transition_and_pml: bool = False,
|
|
1349
1402
|
is_3d: bool = False,
|
|
1403
|
+
in_place: bool = False,
|
|
1350
1404
|
) -> NDArray[np.float64]:
|
|
1351
1405
|
if transit_within_transition_layer and transit_within_pml_layer:
|
|
1352
1406
|
error_msg = (
|
|
@@ -1428,8 +1482,11 @@ class PMLBuilder:
|
|
|
1428
1482
|
if use_gpu:
|
|
1429
1483
|
input_array = xp.asarray(input_array)
|
|
1430
1484
|
|
|
1431
|
-
# Move axis to 0 for uniform processing
|
|
1432
|
-
|
|
1485
|
+
# Move axis to 0 for uniform processing. Only the layers at both ends
|
|
1486
|
+
# are written, so an array the caller owns needs no copy.
|
|
1487
|
+
working_array = xp.moveaxis(input_array, axis, 0)
|
|
1488
|
+
if not in_place:
|
|
1489
|
+
working_array = working_array.copy()
|
|
1433
1490
|
|
|
1434
1491
|
# Apply boundary conditions
|
|
1435
1492
|
working_array[: m_offset + layer_thickness] = value_target
|