fullwave25 1.4.1.dev2__py3-none-any.whl → 1.4.2.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fullwave/__init__.py +1 -1
- fullwave/medium.py +1 -3
- fullwave/solver/binary_manager.py +27 -30
- fullwave/solver/binary_markers.py +87 -0
- fullwave/solver/input_file_writer.py +68 -7
- fullwave/solver/launcher.py +22 -2
- fullwave/solver/solver.py +866 -52
- fullwave/solver/source_type.py +16 -12
- fullwave/source.py +9 -2
- fullwave/transducer.py +24 -19
- fullwave/ultra/__init__.py +15 -0
- fullwave/ultra/backend.py +415 -0
- fullwave/ultra/backend_2d.py +375 -0
- fullwave/ultra/binary.py +152 -0
- fullwave/ultra/dat_io.py +60 -0
- fullwave/ultra/dat_io_3d.py +38 -0
- fullwave/ultra/errors.py +22 -0
- fullwave/ultra/factory.py +21 -0
- fullwave/ultra/medium_to_aexp.py +50 -0
- fullwave/ultra/stencil.py +162 -0
- fullwave/ultra/time_axis.py +61 -0
- fullwave/ultra/writer_2d.py +195 -0
- fullwave/ultra/writer_3d.py +199 -0
- fullwave/utils/__init__.py +2 -1
- fullwave/utils/memory_tempfile.py +21 -0
- {fullwave25-1.4.1.dev2.dist-info → fullwave25-1.4.2.dev1.dist-info}/METADATA +72 -3
- {fullwave25-1.4.1.dev2.dist-info → fullwave25-1.4.2.dev1.dist-info}/RECORD +29 -15
- {fullwave25-1.4.1.dev2.dist-info → fullwave25-1.4.2.dev1.dist-info}/WHEEL +1 -1
- {fullwave25-1.4.1.dev2.dist-info → fullwave25-1.4.2.dev1.dist-info}/licenses/LICENSE +0 -0
fullwave/__init__.py
CHANGED
|
@@ -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.4.
|
|
69
|
+
__version__ = "1.4.2-dev1"
|
|
70
70
|
|
|
71
71
|
VERSION = __version__ # for convenience
|
|
72
72
|
logger.info("Fullwave version: %s", __version__)
|
fullwave/medium.py
CHANGED
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
from collections import OrderedDict
|
|
7
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import TYPE_CHECKING
|
|
@@ -93,8 +93,6 @@ def _upload_arrays_multi_gpu(
|
|
|
93
93
|
dict mapping name -> CuPy array on *target_device*.
|
|
94
94
|
|
|
95
95
|
"""
|
|
96
|
-
from concurrent.futures import as_completed # noqa: PLC0415
|
|
97
|
-
|
|
98
96
|
import cupy as cp # noqa: PLC0415
|
|
99
97
|
|
|
100
98
|
n_gpus = cp.cuda.runtime.getDeviceCount()
|
|
@@ -21,6 +21,8 @@ import urllib.error
|
|
|
21
21
|
import urllib.request
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
|
|
24
|
+
from tqdm import tqdm
|
|
25
|
+
|
|
24
26
|
logger = logging.getLogger("__main__." + __name__)
|
|
25
27
|
|
|
26
28
|
GITHUB_REPO = "pinton-lab/fullwave25"
|
|
@@ -28,7 +30,7 @@ CACHE_DIR = Path.home() / ".cache" / "fullwave25" / "bins"
|
|
|
28
30
|
|
|
29
31
|
# Pinned release tag for the solver binaries.
|
|
30
32
|
# Update this only when new binaries are uploaded to a GitHub release.
|
|
31
|
-
BINARY_RELEASE_TAG = "fullwave_bin_v1.
|
|
33
|
+
BINARY_RELEASE_TAG = "fullwave_bin_v1.7"
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
def _download_url(filename: str, tag: str) -> str:
|
|
@@ -36,39 +38,34 @@ def _download_url(filename: str, tag: str) -> str:
|
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
def _download_binary(url: str, dest: Path) -> None:
|
|
39
|
-
"""Download *url* to *dest*, showing a tqdm progress bar
|
|
41
|
+
"""Download *url* to *dest*, showing a tqdm progress bar."""
|
|
40
42
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
41
43
|
tmp = dest.with_suffix(".download_tmp")
|
|
42
44
|
try:
|
|
45
|
+
|
|
46
|
+
class _Hook:
|
|
47
|
+
def __init__(self) -> None:
|
|
48
|
+
self._bar: tqdm | None = None
|
|
49
|
+
|
|
50
|
+
def __call__(self, block: int, block_size: int, total: int) -> None: # noqa: ARG002
|
|
51
|
+
if self._bar is None:
|
|
52
|
+
self._bar = tqdm(
|
|
53
|
+
total=total if total > 0 else None,
|
|
54
|
+
unit="B",
|
|
55
|
+
unit_scale=True,
|
|
56
|
+
desc=dest.name,
|
|
57
|
+
)
|
|
58
|
+
self._bar.update(block_size)
|
|
59
|
+
|
|
60
|
+
def close(self) -> None:
|
|
61
|
+
if self._bar is not None:
|
|
62
|
+
self._bar.close()
|
|
63
|
+
|
|
64
|
+
hook = _Hook()
|
|
43
65
|
try:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def __init__(self) -> None:
|
|
48
|
-
self._bar: tqdm | None = None
|
|
49
|
-
|
|
50
|
-
def __call__(self, block: int, block_size: int, total: int) -> None: # noqa: ARG002
|
|
51
|
-
if self._bar is None:
|
|
52
|
-
self._bar = tqdm(
|
|
53
|
-
total=total if total > 0 else None,
|
|
54
|
-
unit="B",
|
|
55
|
-
unit_scale=True,
|
|
56
|
-
desc=dest.name,
|
|
57
|
-
)
|
|
58
|
-
self._bar.update(block_size)
|
|
59
|
-
|
|
60
|
-
def close(self) -> None:
|
|
61
|
-
if self._bar is not None:
|
|
62
|
-
self._bar.close()
|
|
63
|
-
|
|
64
|
-
hook = _Hook()
|
|
65
|
-
try:
|
|
66
|
-
urllib.request.urlretrieve(url, tmp, reporthook=hook) # noqa: S310
|
|
67
|
-
finally:
|
|
68
|
-
hook.close()
|
|
69
|
-
|
|
70
|
-
except ImportError:
|
|
71
|
-
urllib.request.urlretrieve(url, tmp) # noqa: S310
|
|
66
|
+
urllib.request.urlretrieve(url, tmp, reporthook=hook) # noqa: S310
|
|
67
|
+
finally:
|
|
68
|
+
hook.close()
|
|
72
69
|
|
|
73
70
|
tmp.rename(dest)
|
|
74
71
|
dest.chmod(dest.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Find out whether a solver binary reads an input file, by the file name in its bytes."""
|
|
2
|
+
|
|
3
|
+
import mmap
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import NamedTuple
|
|
7
|
+
|
|
8
|
+
HOLD_SOURCE_MARKER = b"hold_source.dat"
|
|
9
|
+
DEFAULT_FIX = "rebuild the binary, or set FULLWAVE25_BINARY_TAG to the release that has it"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HoldSourceUnsupportedError(RuntimeError):
|
|
13
|
+
"""Raised when the binary cannot hold the hard source after the drive ends."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _FileStamp(NamedTuple):
|
|
17
|
+
"""A file with the size and modification time that tell whether it changed."""
|
|
18
|
+
|
|
19
|
+
path: Path
|
|
20
|
+
size: int
|
|
21
|
+
mtime_ns: int
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@lru_cache(maxsize=64)
|
|
25
|
+
def _find(stamp: _FileStamp, marker: bytes) -> bool:
|
|
26
|
+
if stamp.size == 0:
|
|
27
|
+
return False
|
|
28
|
+
with stamp.path.open("rb") as file, mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as mm:
|
|
29
|
+
return mm.find(marker) != -1
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def binary_has_marker(binary: str | Path, *, marker: bytes) -> bool:
|
|
33
|
+
"""Return whether the bytes of ``binary`` contain ``marker``.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
binary : str | Path
|
|
38
|
+
The solver binary.
|
|
39
|
+
marker : bytes
|
|
40
|
+
The name of the input file the binary reads.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
bool
|
|
45
|
+
True when the binary contains the name. The answer is cached until the file changes.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
path = Path(binary).resolve()
|
|
49
|
+
stat = path.stat()
|
|
50
|
+
return _find(_FileStamp(path=path, size=stat.st_size, mtime_ns=stat.st_mtime_ns), marker)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def check_binary_holds_source(
|
|
54
|
+
binary: str | Path,
|
|
55
|
+
*,
|
|
56
|
+
marker: bytes = HOLD_SOURCE_MARKER,
|
|
57
|
+
error_type: type[Exception] = HoldSourceUnsupportedError,
|
|
58
|
+
fix: str = DEFAULT_FIX,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Refuse a binary that would release the hard source cells when the drive ends.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
binary : str | Path
|
|
65
|
+
The solver binary.
|
|
66
|
+
marker : bytes, optional
|
|
67
|
+
The name of the input file that tells the binary to hold the source.
|
|
68
|
+
error_type : type[Exception], optional
|
|
69
|
+
The exception to raise.
|
|
70
|
+
fix : str, optional
|
|
71
|
+
What to do to get a binary that reads the file, worded for the message.
|
|
72
|
+
|
|
73
|
+
Raises
|
|
74
|
+
------
|
|
75
|
+
Exception
|
|
76
|
+
``error_type``, when the binary does not contain ``marker``.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
if binary_has_marker(binary, marker=marker):
|
|
80
|
+
return
|
|
81
|
+
message = (
|
|
82
|
+
f"{Path(binary).name} does not read {marker.decode()}, so it releases the hard source "
|
|
83
|
+
"cells when the drive signal ends and source_type='hard' would not hold them. "
|
|
84
|
+
f"Use a binary that reads it: {fix}. "
|
|
85
|
+
"source_type='hard_released_after' needs no such binary."
|
|
86
|
+
)
|
|
87
|
+
raise error_type(message)
|
|
@@ -4,6 +4,8 @@ import concurrent.futures
|
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
6
|
import time
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
|
|
9
11
|
import numpy as np
|
|
@@ -63,6 +65,7 @@ class InputFileWriter:
|
|
|
63
65
|
exponential_attenuation_pml_interior_offset_px: int = 0,
|
|
64
66
|
use_gpu: bool = False,
|
|
65
67
|
write_maps_the_solver_does_not_read: bool = False,
|
|
68
|
+
hold_hard_source: bool = False,
|
|
66
69
|
) -> None:
|
|
67
70
|
"""Initialize the InputGeneratorBase instance.
|
|
68
71
|
|
|
@@ -123,6 +126,11 @@ class InputFileWriter:
|
|
|
123
126
|
which an older solver binary needs. The record the skipped maps
|
|
124
127
|
replace is impedance_constraint.dat, and this writer always writes it
|
|
125
128
|
on the relaxation path.
|
|
129
|
+
hold_hard_source : bool, optional
|
|
130
|
+
Whether the hard source cells stay at zero pressure after the drive ends.
|
|
131
|
+
True writes the flag file ``hold_source.dat``, which the solver reads to keep
|
|
132
|
+
the cells at zero for the rest of the run; the signals are written as given.
|
|
133
|
+
False, the default, lets the solver release the cells when the signal ends.
|
|
126
134
|
|
|
127
135
|
"""
|
|
128
136
|
logger.debug("Initializing InputFileWriter instance.")
|
|
@@ -138,6 +146,7 @@ class InputFileWriter:
|
|
|
138
146
|
self.use_isotropic_relaxation = use_isotropic_relaxation
|
|
139
147
|
self.write_maps_the_solver_does_not_read = write_maps_the_solver_does_not_read
|
|
140
148
|
self.use_gpu = use_gpu
|
|
149
|
+
self.hold_hard_source = hold_hard_source
|
|
141
150
|
|
|
142
151
|
if validate_input:
|
|
143
152
|
check_functions.check_path_exists(self.path_fullwave_simulation_bin)
|
|
@@ -202,6 +211,7 @@ class InputFileWriter:
|
|
|
202
211
|
*,
|
|
203
212
|
is_static_map: bool = False,
|
|
204
213
|
recalculate_pml: bool = True,
|
|
214
|
+
members: Sequence[fullwave.Source] | None = None,
|
|
205
215
|
) -> Path:
|
|
206
216
|
r"""Run the input data generation and return the simulation directory path.
|
|
207
217
|
|
|
@@ -230,6 +240,11 @@ class InputFileWriter:
|
|
|
230
240
|
set False if you are using the same PML parameters for each transmit event
|
|
231
241
|
such as plane wave
|
|
232
242
|
AND this is the second or later transmit event.
|
|
243
|
+
members : Sequence[fullwave.Source] | None
|
|
244
|
+
The transmits of a batch run, in order. Each member gives its own signals and
|
|
245
|
+
shares the coordinates of ``source``. The signals of all members are written one
|
|
246
|
+
after another, and nsims.dat holds the member count. None writes a single
|
|
247
|
+
transmit from ``source``.
|
|
233
248
|
|
|
234
249
|
Returns
|
|
235
250
|
-------
|
|
@@ -243,11 +258,19 @@ class InputFileWriter:
|
|
|
243
258
|
|
|
244
259
|
self._init_pending_writes()
|
|
245
260
|
|
|
261
|
+
member_count_file = simulation_dir / "nsims.dat"
|
|
262
|
+
if members is None:
|
|
263
|
+
# A file left by an earlier batch run in this directory would turn this run into
|
|
264
|
+
# a batch.
|
|
265
|
+
member_count_file.unlink(missing_ok=True)
|
|
266
|
+
else:
|
|
267
|
+
self._queue_v_abs_write(np.int32, member_count_file, len(members))
|
|
268
|
+
|
|
246
269
|
self._queue_ic_write(
|
|
247
270
|
simulation_dir / "icmat.dat",
|
|
248
|
-
np.transpose(self.
|
|
271
|
+
np.transpose(self._signals("icmat", members)),
|
|
249
272
|
)
|
|
250
|
-
p0_additive =
|
|
273
|
+
p0_additive = self._signals("p0_additive", members)
|
|
251
274
|
incoords_add = getattr(self.source, "incoords_add", None)
|
|
252
275
|
if p0_additive is not None:
|
|
253
276
|
self._queue_ic_write(
|
|
@@ -257,7 +280,7 @@ class InputFileWriter:
|
|
|
257
280
|
if incoords_add is not None:
|
|
258
281
|
self._queue_coords_write(simulation_dir / "icc_add.dat", incoords_add)
|
|
259
282
|
for _vel_suffix, _vel_attr in (("u", "u0"), ("v", "v0"), ("w", "w0")):
|
|
260
|
-
_signal_vel =
|
|
283
|
+
_signal_vel = self._signals(_vel_attr, members)
|
|
261
284
|
_incoords_vel = getattr(self.source, f"incoords_{_vel_suffix}", None)
|
|
262
285
|
if _signal_vel is not None:
|
|
263
286
|
self._queue_ic_write(
|
|
@@ -310,6 +333,44 @@ class InputFileWriter:
|
|
|
310
333
|
logger.info(message)
|
|
311
334
|
return simulation_dir
|
|
312
335
|
|
|
336
|
+
def _signals(
|
|
337
|
+
self,
|
|
338
|
+
name: str,
|
|
339
|
+
members: Sequence[fullwave.Source] | None,
|
|
340
|
+
) -> np.ndarray | None:
|
|
341
|
+
"""Return one source signal matrix, with the rows of every member stacked in order.
|
|
342
|
+
|
|
343
|
+
Parameters
|
|
344
|
+
----------
|
|
345
|
+
name : str
|
|
346
|
+
The signal attribute of fullwave.Source: icmat, p0_additive, u0, v0 or w0.
|
|
347
|
+
members : Sequence[fullwave.Source] | None
|
|
348
|
+
The transmits of a batch run, or None for the single transmit of ``source``.
|
|
349
|
+
|
|
350
|
+
Returns
|
|
351
|
+
-------
|
|
352
|
+
np.ndarray | None
|
|
353
|
+
[n_points, n_t] for a single transmit, [n_members * n_points, n_t] for a batch,
|
|
354
|
+
or None when the source has no signal of this kind.
|
|
355
|
+
|
|
356
|
+
"""
|
|
357
|
+
if members is None:
|
|
358
|
+
signal = getattr(self.source, name, None)
|
|
359
|
+
elif getattr(self.source, name, None) is None:
|
|
360
|
+
return None
|
|
361
|
+
else:
|
|
362
|
+
signal = np.concatenate([getattr(member, name) for member in members], axis=0)
|
|
363
|
+
return signal
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def _holds_the_source(self) -> bool:
|
|
367
|
+
"""Whether the solver keeps the hard source cells at zero after the drive ends."""
|
|
368
|
+
return (
|
|
369
|
+
self.hold_hard_source
|
|
370
|
+
and self.source.n_sources > 0
|
|
371
|
+
and self.source.icmat.shape[1] < self.grid.nt
|
|
372
|
+
)
|
|
373
|
+
|
|
313
374
|
# --- constructor utils ---
|
|
314
375
|
|
|
315
376
|
def _set_d_mat(self) -> None:
|
|
@@ -867,8 +928,6 @@ class InputFileWriter:
|
|
|
867
928
|
(c_min_val, c_max_val) of the sound speed.
|
|
868
929
|
|
|
869
930
|
"""
|
|
870
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed # noqa: PLC0415
|
|
871
|
-
|
|
872
931
|
import cupy as cp # noqa: PLC0415
|
|
873
932
|
|
|
874
933
|
sound_speed_np = self.medium.sound_speed
|
|
@@ -1217,6 +1276,7 @@ class InputFileWriter:
|
|
|
1217
1276
|
"ncoordsout",
|
|
1218
1277
|
"ncoordszero",
|
|
1219
1278
|
"nTic",
|
|
1279
|
+
"hold_source",
|
|
1220
1280
|
"modT",
|
|
1221
1281
|
"modX",
|
|
1222
1282
|
"modY",
|
|
@@ -1315,7 +1375,6 @@ class InputFileWriter:
|
|
|
1315
1375
|
self._queue_v_abs_write(np.float32, save_path, var)
|
|
1316
1376
|
|
|
1317
1377
|
def _save_coords_params(self, simulation_dir: Path) -> None:
|
|
1318
|
-
nt_ic = self.source.icmat.shape[1]
|
|
1319
1378
|
var_list = [
|
|
1320
1379
|
("nX", self.grid.nx),
|
|
1321
1380
|
("nY", self.grid.ny),
|
|
@@ -1323,9 +1382,11 @@ class InputFileWriter:
|
|
|
1323
1382
|
("ncoords", self.source.n_sources),
|
|
1324
1383
|
("ncoordsout", self.sensor.n_sensors),
|
|
1325
1384
|
("ncoordszero", self.medium.n_air),
|
|
1326
|
-
("nTic",
|
|
1385
|
+
("nTic", self.source.icmat.shape[1]),
|
|
1327
1386
|
("modT", self.sensor.sampling_modulus_time),
|
|
1328
1387
|
]
|
|
1388
|
+
if self._holds_the_source:
|
|
1389
|
+
var_list.append(("hold_source", 1))
|
|
1329
1390
|
n_sources_add = getattr(self.source, "n_sources_add", 0)
|
|
1330
1391
|
if n_sources_add > 0:
|
|
1331
1392
|
var_list.append(("ncoords_add", n_sources_add))
|
fullwave/solver/launcher.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Module for launching Fullwave simulation."""
|
|
2
2
|
|
|
3
|
+
import copy
|
|
3
4
|
import ctypes
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
@@ -12,6 +13,7 @@ from time import time
|
|
|
12
13
|
import numpy as np
|
|
13
14
|
from numpy.typing import NDArray
|
|
14
15
|
|
|
16
|
+
from .cuda_utils import find_consecutive_gpus_without_peer_access
|
|
15
17
|
from .utils import load_dat_data
|
|
16
18
|
|
|
17
19
|
logger = logging.getLogger("__main__." + __name__)
|
|
@@ -85,6 +87,26 @@ class Launcher:
|
|
|
85
87
|
self.save_gpu_memory = save_gpu_memory
|
|
86
88
|
logger.debug("Launcher instance created.")
|
|
87
89
|
|
|
90
|
+
def with_binary(self, path_fullwave_simulation_bin: Path) -> "Launcher":
|
|
91
|
+
"""Return a copy of this launcher that runs another binary.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
path_fullwave_simulation_bin : Path
|
|
96
|
+
The binary of the copy.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
Launcher
|
|
101
|
+
The copy, with the same devices and memory settings.
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
error_msg = f"Fullwave simulation binary not found at {path_fullwave_simulation_bin}"
|
|
105
|
+
assert path_fullwave_simulation_bin.exists(), error_msg
|
|
106
|
+
launcher = copy.copy(self)
|
|
107
|
+
launcher._path_fullwave_simulation_bin = path_fullwave_simulation_bin
|
|
108
|
+
return launcher
|
|
109
|
+
|
|
88
110
|
@staticmethod
|
|
89
111
|
def _parse_cuda_device_id(cuda_device_id: str | int | list | None) -> str:
|
|
90
112
|
"""Parse the CUDA device ID input into a string format.
|
|
@@ -190,8 +212,6 @@ class Launcher:
|
|
|
190
212
|
)
|
|
191
213
|
return
|
|
192
214
|
try:
|
|
193
|
-
from .cuda_utils import find_consecutive_gpus_without_peer_access # noqa: PLC0415
|
|
194
|
-
|
|
195
215
|
pairs = find_consecutive_gpus_without_peer_access(device_ids)
|
|
196
216
|
except (AttributeError, NameError, OSError, RuntimeError, ctypes.ArgumentError) as error:
|
|
197
217
|
logger.debug("Peer access check skipped: %s", error)
|