fullwave25 1.4.2.dev2__py3-none-any.whl → 1.4.2.dev4__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/beamformer/beamformer.py +23 -5
- fullwave/solver/input_file_writer.py +22 -5
- fullwave/solver/solver.py +137 -14
- fullwave/transducer.py +316 -52
- fullwave/ultra/attenuation_parameters.py +215 -0
- fullwave/ultra/backend.py +85 -5
- fullwave/ultra/backend_2d.py +45 -7
- fullwave/ultra/binary.py +28 -1
- fullwave/ultra/factory.py +4 -1
- fullwave/ultra/medium_to_aexp.py +123 -6
- fullwave/ultra/writer_2d.py +54 -14
- fullwave/ultra/writer_3d.py +54 -14
- fullwave/utils/relaxation_parameters.py +51 -0
- fullwave/utils/signal_filter.py +71 -106
- {fullwave25-1.4.2.dev2.dist-info → fullwave25-1.4.2.dev4.dist-info}/METADATA +35 -7
- {fullwave25-1.4.2.dev2.dist-info → fullwave25-1.4.2.dev4.dist-info}/RECORD +19 -18
- {fullwave25-1.4.2.dev2.dist-info → fullwave25-1.4.2.dev4.dist-info}/WHEEL +0 -0
- {fullwave25-1.4.2.dev2.dist-info → fullwave25-1.4.2.dev4.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.2-
|
|
69
|
+
__version__ = "1.4.2-dev4"
|
|
70
70
|
|
|
71
71
|
VERSION = __version__ # for convenience
|
|
72
72
|
logger.info("Fullwave version: %s", __version__)
|
|
@@ -14,6 +14,14 @@ class Beamformer:
|
|
|
14
14
|
For faster implementations, consider using libraries such as
|
|
15
15
|
mach beamformer: https://github.com/Forest-Neurotech/mach
|
|
16
16
|
|
|
17
|
+
Conventions: ``axial_position_m`` is the depth below the probe face, so
|
|
18
|
+
depth 0 is the face row ``face_row_px`` (``Transducer.face_row_px``, the
|
|
19
|
+
first medium row, one row in front of the receiver row).
|
|
20
|
+
``transducer_coordinates`` must be the receiver positions in grid points
|
|
21
|
+
(for example from ``Transducer.element_id_to_receiver_center``). The time
|
|
22
|
+
origin is the mean receiver row, so the array depth is subtracted from the
|
|
23
|
+
two-way travel time.
|
|
24
|
+
|
|
17
25
|
References
|
|
18
26
|
----------
|
|
19
27
|
- Fullwave2 BMME890 implementation
|
|
@@ -31,6 +39,7 @@ class Beamformer:
|
|
|
31
39
|
axial_position_m: np.ndarray,
|
|
32
40
|
num_elements: int,
|
|
33
41
|
transducer_coordinates: np.ndarray,
|
|
42
|
+
face_row_px: int,
|
|
34
43
|
f_number: float = 1.0,
|
|
35
44
|
) -> None:
|
|
36
45
|
"""Initialize the beamformer with simulation and transducer parameters.
|
|
@@ -46,11 +55,14 @@ class Beamformer:
|
|
|
46
55
|
lateral_position_m : np.ndarray
|
|
47
56
|
Lateral positions (m) where the beamformed image will be computed.
|
|
48
57
|
axial_position_m : np.ndarray
|
|
49
|
-
|
|
58
|
+
Depths (m) below the probe face where the image is computed.
|
|
50
59
|
num_elements : int
|
|
51
60
|
Number of transducer elements.
|
|
52
61
|
transducer_coordinates : np.ndarray
|
|
53
|
-
|
|
62
|
+
Receiver coordinates [n_elements, 2] in grid points (row, column).
|
|
63
|
+
face_row_px : int
|
|
64
|
+
Grid row of the probe face (depth 0), for example
|
|
65
|
+
``Transducer.face_row_px``.
|
|
54
66
|
f_number : float, optional
|
|
55
67
|
F-number for aperture calculation, by default 1.0.
|
|
56
68
|
|
|
@@ -62,6 +74,7 @@ class Beamformer:
|
|
|
62
74
|
self.axial_position_m = axial_position_m
|
|
63
75
|
self.num_elements = num_elements
|
|
64
76
|
self.transducer_coordinates = transducer_coordinates
|
|
77
|
+
self.face_row_px = face_row_px
|
|
65
78
|
self.f_number = f_number
|
|
66
79
|
|
|
67
80
|
def run(self, signals: np.ndarray) -> np.ndarray:
|
|
@@ -76,13 +89,17 @@ class Beamformer:
|
|
|
76
89
|
Returns
|
|
77
90
|
-------
|
|
78
91
|
np.ndarray
|
|
79
|
-
Beamformed image. Shape: [n_axial, n_lateral, n_transmit].
|
|
92
|
+
Beamformed image. Shape: [n_axial, n_lateral, n_transmit]. Row 0
|
|
93
|
+
of the first axis is the first depth of ``axial_position_m``, which
|
|
94
|
+
is measured from the probe face.
|
|
80
95
|
|
|
81
96
|
"""
|
|
82
97
|
# signals: [n_elements, n_time, n_transmit]
|
|
83
98
|
if signals.ndim == 2:
|
|
84
99
|
signals = signals[:, :, np.newaxis]
|
|
85
100
|
n_transmit = signals.shape[2]
|
|
101
|
+
# idt_0 is the pulse arrival at the receivers, so time is measured from the array.
|
|
102
|
+
z_array = np.mean(self.transducer_coordinates[:, 0]) * self.dx
|
|
86
103
|
|
|
87
104
|
hilbert_signals = hilbert(signals, axis=1)
|
|
88
105
|
beamformed_image = np.zeros(
|
|
@@ -101,12 +118,13 @@ class Beamformer:
|
|
|
101
118
|
desc="Lateral positions",
|
|
102
119
|
total=len(self.lateral_position_m),
|
|
103
120
|
):
|
|
104
|
-
for i_axial,
|
|
121
|
+
for i_axial, depth in tqdm(
|
|
105
122
|
enumerate(self.axial_position_m),
|
|
106
123
|
desc="Axial positions",
|
|
107
124
|
total=len(self.axial_position_m),
|
|
108
125
|
leave=False,
|
|
109
126
|
):
|
|
127
|
+
axial = self.face_row_px * self.dx + depth
|
|
110
128
|
fcen = np.array(
|
|
111
129
|
[
|
|
112
130
|
int(axial / self.dx),
|
|
@@ -133,7 +151,7 @@ class Beamformer:
|
|
|
133
151
|
dd = np.round(dd / (self.dt / self.dx * self.c0)).astype(int)
|
|
134
152
|
dd = dd - dd.min()
|
|
135
153
|
|
|
136
|
-
idt = idt_0 + int(2 * axial / self.c0 / self.dt)
|
|
154
|
+
idt = idt_0 + int(2 * (axial - z_array) / self.c0 / self.dt)
|
|
137
155
|
|
|
138
156
|
# idp: 1D indices into signals flattened array
|
|
139
157
|
# we convert 2D indices to 1D indices
|
|
@@ -293,6 +293,7 @@ class InputFileWriter:
|
|
|
293
293
|
_incoords_vel,
|
|
294
294
|
)
|
|
295
295
|
self._queue_coords_write(simulation_dir / "icc.dat", self.source.incoords)
|
|
296
|
+
self._save_drive_params(simulation_dir, members)
|
|
296
297
|
self._copy_simulation_bin_file(simulation_dir)
|
|
297
298
|
|
|
298
299
|
if not self.use_exponential_attenuation:
|
|
@@ -1275,8 +1276,6 @@ class InputFileWriter:
|
|
|
1275
1276
|
"ncoords_w",
|
|
1276
1277
|
"ncoordsout",
|
|
1277
1278
|
"ncoordszero",
|
|
1278
|
-
"nTic",
|
|
1279
|
-
"hold_source",
|
|
1280
1279
|
"modT",
|
|
1281
1280
|
"modX",
|
|
1282
1281
|
"modY",
|
|
@@ -1374,6 +1373,27 @@ class InputFileWriter:
|
|
|
1374
1373
|
save_path = simulation_dir / f"{var_name}.dat"
|
|
1375
1374
|
self._queue_v_abs_write(np.float32, save_path, var)
|
|
1376
1375
|
|
|
1376
|
+
def _save_drive_params(
|
|
1377
|
+
self,
|
|
1378
|
+
simulation_dir: Path,
|
|
1379
|
+
members: Sequence[fullwave.Source] | None = None,
|
|
1380
|
+
) -> None:
|
|
1381
|
+
"""Write the drive length and the hold flag of this transmit.
|
|
1382
|
+
|
|
1383
|
+
Every transmit folder gets its own files, never a link to the shared
|
|
1384
|
+
static-map files, and a flag left by an earlier run is removed when the
|
|
1385
|
+
hold is off. The members of a batch share one length, which can exceed the
|
|
1386
|
+
length of the source, so the drive length is the one of the members.
|
|
1387
|
+
"""
|
|
1388
|
+
drive_length_file = simulation_dir / "nTic.dat"
|
|
1389
|
+
hold_flag_file = simulation_dir / "hold_source.dat"
|
|
1390
|
+
drive_length_file.unlink(missing_ok=True)
|
|
1391
|
+
hold_flag_file.unlink(missing_ok=True)
|
|
1392
|
+
drive_source = self.source if members is None else members[0]
|
|
1393
|
+
self._queue_v_abs_write(np.int32, drive_length_file, drive_source.icmat.shape[1])
|
|
1394
|
+
if self._holds_the_source:
|
|
1395
|
+
self._queue_v_abs_write(np.int32, hold_flag_file, 1)
|
|
1396
|
+
|
|
1377
1397
|
def _save_coords_params(self, simulation_dir: Path) -> None:
|
|
1378
1398
|
var_list = [
|
|
1379
1399
|
("nX", self.grid.nx),
|
|
@@ -1382,11 +1402,8 @@ class InputFileWriter:
|
|
|
1382
1402
|
("ncoords", self.source.n_sources),
|
|
1383
1403
|
("ncoordsout", self.sensor.n_sensors),
|
|
1384
1404
|
("ncoordszero", self.medium.n_air),
|
|
1385
|
-
("nTic", self.source.icmat.shape[1]),
|
|
1386
1405
|
("modT", self.sensor.sampling_modulus_time),
|
|
1387
1406
|
]
|
|
1388
|
-
if self._holds_the_source:
|
|
1389
|
-
var_list.append(("hold_source", 1))
|
|
1390
1407
|
n_sources_add = getattr(self.source, "n_sources_add", 0)
|
|
1391
1408
|
if n_sources_add > 0:
|
|
1392
1409
|
var_list.append(("ncoords_add", n_sources_add))
|
fullwave/solver/solver.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""solver module."""
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
|
+
import copy
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
6
7
|
import time
|
|
@@ -22,6 +23,7 @@ from fullwave.solver.source_type import (
|
|
|
22
23
|
is_hard_held,
|
|
23
24
|
is_soft,
|
|
24
25
|
)
|
|
26
|
+
from fullwave.ultra import attenuation_parameters as power_law_attenuation
|
|
25
27
|
from fullwave.ultra import backend, binary, factory
|
|
26
28
|
from fullwave.utils import (
|
|
27
29
|
check_functions,
|
|
@@ -470,10 +472,12 @@ class Solver:
|
|
|
470
472
|
verify_gpu: bool = True,
|
|
471
473
|
use_gpu_pml: bool = False,
|
|
472
474
|
pml_alpha_entrance: float | None = None,
|
|
473
|
-
source_type: str =
|
|
475
|
+
source_type: str | None = None,
|
|
474
476
|
write_maps_the_solver_does_not_read: bool = False,
|
|
475
477
|
use_ultra: bool = False,
|
|
476
478
|
ultra_sponge_thickness_px: int | None = None,
|
|
479
|
+
ultra_attenuation_parameters: str | Path | None = None,
|
|
480
|
+
ultra_unlimited_materials: bool = False,
|
|
477
481
|
) -> None:
|
|
478
482
|
"""Initialize a Solver instance for the fullwave simulation.
|
|
479
483
|
|
|
@@ -599,12 +603,14 @@ class Solver:
|
|
|
599
603
|
relaxation path. Set it to True for a solver binary that predates
|
|
600
604
|
that record. Such a binary stops with an error on kappax.dat rather
|
|
601
605
|
than returning a wrong answer.
|
|
602
|
-
source_type : str, optional
|
|
603
|
-
How the source drives the field.
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
606
|
+
source_type : str | None, optional
|
|
607
|
+
How the source drives the field. None, the default, takes the type of the
|
|
608
|
+
``transducer`` when one is given, and "hard_released_after" otherwise. A
|
|
609
|
+
value that differs from the transducer's type raises ValueError.
|
|
610
|
+
"hard_released_after" assigns the source pressure while the drive signal
|
|
611
|
+
plays, then lets the source cells evolve freely. "hard" assigns it for the
|
|
612
|
+
whole run, so the cells stay at zero pressure after the signal ends.
|
|
613
|
+
"soft" adds the signal to the field. ``use_ultra`` follows the same three values.
|
|
608
614
|
use_ultra : bool, optional
|
|
609
615
|
Experimental. Run a user-supplied fullwave2-ultra binary (not shipped; pass it
|
|
610
616
|
as ``path_fullwave_simulation_bin`` or set ``FW2U_BATCH_BIN``): ``bench_2d_batch``
|
|
@@ -620,6 +626,13 @@ class Solver:
|
|
|
620
626
|
Width of the absorbing sponge of the ``use_ultra`` backend, in cells.
|
|
621
627
|
None, the default, gives 6 wavelengths in 2D and 4 wavelengths in 3D.
|
|
622
628
|
Used only with ``use_ultra=True``.
|
|
629
|
+
ultra_attenuation_parameters : str | Path, optional
|
|
630
|
+
Power-law attenuation for the ``use_ultra`` backend. Give "centered", "author",
|
|
631
|
+
or the path of a parameter file. Needs ``use_ultra=True`` and
|
|
632
|
+
``use_exponential_attenuation=False``.
|
|
633
|
+
ultra_unlimited_materials : bool, optional
|
|
634
|
+
If True, allow any number of distinct materials (uses more GPU memory). Needs
|
|
635
|
+
``ultra_attenuation_parameters``.
|
|
623
636
|
|
|
624
637
|
Raises
|
|
625
638
|
------
|
|
@@ -630,9 +643,28 @@ class Solver:
|
|
|
630
643
|
and transducer) are defined simultaneously.
|
|
631
644
|
|
|
632
645
|
"""
|
|
646
|
+
source_type = self._resolve_source_type(source_type=source_type, transducer=transducer)
|
|
633
647
|
self.ultra = None
|
|
648
|
+
power_law_attenuation.check_unlimited_materials(
|
|
649
|
+
unlimited_materials=ultra_unlimited_materials,
|
|
650
|
+
parameters=ultra_attenuation_parameters,
|
|
651
|
+
use_ultra=use_ultra,
|
|
652
|
+
)
|
|
653
|
+
if ultra_attenuation_parameters is not None:
|
|
654
|
+
request = power_law_attenuation.check_request(
|
|
655
|
+
parameters=ultra_attenuation_parameters,
|
|
656
|
+
use_ultra=use_ultra,
|
|
657
|
+
use_exponential_attenuation=use_exponential_attenuation,
|
|
658
|
+
binary=path_fullwave_simulation_bin,
|
|
659
|
+
is_3d=grid.is_3d,
|
|
660
|
+
unlimited_materials=ultra_unlimited_materials,
|
|
661
|
+
)
|
|
662
|
+
ultra_attenuation_parameters = request.parameter_file
|
|
663
|
+
path_fullwave_simulation_bin = request.binary
|
|
634
664
|
if use_ultra:
|
|
635
665
|
self._init_ultra(
|
|
666
|
+
attenuation_parameters=ultra_attenuation_parameters,
|
|
667
|
+
unlimited_materials=ultra_unlimited_materials,
|
|
636
668
|
work_dir=work_dir,
|
|
637
669
|
grid=grid,
|
|
638
670
|
medium=medium,
|
|
@@ -813,7 +845,12 @@ class Solver:
|
|
|
813
845
|
logger.error(error_msg)
|
|
814
846
|
raise ValueError(error_msg)
|
|
815
847
|
self.source_type = source_type
|
|
816
|
-
|
|
848
|
+
source_is_the_transducers = source is None and transducer is not None
|
|
849
|
+
if is_soft(source_type) and source_is_the_transducers:
|
|
850
|
+
logger.info(
|
|
851
|
+
"source_type=%s, the transducer's source is driven by addition", source_type
|
|
852
|
+
)
|
|
853
|
+
elif is_soft(source_type) and self.source.p0_additive is not None:
|
|
817
854
|
error_msg = (
|
|
818
855
|
"this source already carries an additive signal. source_type='soft' "
|
|
819
856
|
"converts a hard source into a soft one, and this source holds no "
|
|
@@ -824,7 +861,7 @@ class Solver:
|
|
|
824
861
|
raise ValueError(error_msg)
|
|
825
862
|
if not is_soft(source_type) and self.source.p0_additive is not None:
|
|
826
863
|
self._warn_if_the_additive_signal_was_not_scaled()
|
|
827
|
-
if is_soft(source_type):
|
|
864
|
+
if is_soft(source_type) and not source_is_the_transducers:
|
|
828
865
|
self.source = as_additive_source(
|
|
829
866
|
self.source,
|
|
830
867
|
self.grid,
|
|
@@ -898,11 +935,15 @@ class Solver:
|
|
|
898
935
|
verify_gpu: bool,
|
|
899
936
|
nbdy: int | None,
|
|
900
937
|
options: dict,
|
|
938
|
+
attenuation_parameters: Path | None,
|
|
939
|
+
unlimited_materials: bool,
|
|
901
940
|
) -> None:
|
|
902
941
|
"""Set up the experimental fullwave2-ultra backend (see ``fullwave.ultra``)."""
|
|
903
942
|
backend.warn_experimental()
|
|
904
943
|
backend.warn_ppw_above_speed_optimum(ppw=grid.ppw)
|
|
905
|
-
backend.check_solver_options(
|
|
944
|
+
backend.check_solver_options(
|
|
945
|
+
**options, has_attenuation_parameters=attenuation_parameters is not None
|
|
946
|
+
)
|
|
906
947
|
binary_name = binary.BINARY_NAME_3D if grid.is_3d else binary.BINARY_NAME_2D
|
|
907
948
|
ultra_binary = binary.resolve_binary(path_fullwave_simulation_bin, name=binary_name)
|
|
908
949
|
self._check_input(grid, medium, source, sensor, transducer, ultra_binary)
|
|
@@ -924,8 +965,48 @@ class Solver:
|
|
|
924
965
|
cuda_device_id=cuda_device_id,
|
|
925
966
|
verify_gpu=verify_gpu,
|
|
926
967
|
source_type=options["source_type"],
|
|
968
|
+
attenuation_parameters=attenuation_parameters,
|
|
969
|
+
unlimited_materials=unlimited_materials,
|
|
927
970
|
)
|
|
928
971
|
|
|
972
|
+
@staticmethod
|
|
973
|
+
def _resolve_source_type(
|
|
974
|
+
*, source_type: str | None, transducer: fullwave.Transducer | None
|
|
975
|
+
) -> str:
|
|
976
|
+
"""Return the source type of the run.
|
|
977
|
+
|
|
978
|
+
Parameters
|
|
979
|
+
----------
|
|
980
|
+
source_type : str | None
|
|
981
|
+
The Solver's own argument.
|
|
982
|
+
transducer : fullwave.Transducer | None
|
|
983
|
+
The transducer that drives the run, if any.
|
|
984
|
+
|
|
985
|
+
Returns
|
|
986
|
+
-------
|
|
987
|
+
str
|
|
988
|
+
The transducer's type when the argument is None and a transducer is given,
|
|
989
|
+
the argument when it is given, and "hard_released_after" otherwise.
|
|
990
|
+
|
|
991
|
+
Raises
|
|
992
|
+
------
|
|
993
|
+
ValueError
|
|
994
|
+
If the argument differs from the transducer's type.
|
|
995
|
+
"""
|
|
996
|
+
if transducer is None:
|
|
997
|
+
return HARD_RELEASED_AFTER if source_type is None else source_type
|
|
998
|
+
if source_type is None:
|
|
999
|
+
return transducer.source_type
|
|
1000
|
+
if source_type != transducer.source_type:
|
|
1001
|
+
error_msg = (
|
|
1002
|
+
f"Solver source_type {source_type!r} differs from the transducer's "
|
|
1003
|
+
f"source_type {transducer.source_type!r}. Set the source type in one "
|
|
1004
|
+
"place, on the Transducer, and leave the Solver's source_type unset."
|
|
1005
|
+
)
|
|
1006
|
+
logger.error(error_msg)
|
|
1007
|
+
raise ValueError(error_msg)
|
|
1008
|
+
return source_type
|
|
1009
|
+
|
|
929
1010
|
def _warn_if_the_additive_signal_was_not_scaled(self) -> None:
|
|
930
1011
|
"""Say so when an additive signal was built by hand and may carry no scale.
|
|
931
1012
|
|
|
@@ -1541,8 +1622,10 @@ class Solver:
|
|
|
1541
1622
|
Parameters
|
|
1542
1623
|
----------
|
|
1543
1624
|
sources : Sequence[fullwave.Source]
|
|
1544
|
-
The transmits, in order. Each one must have the same source coordinates
|
|
1545
|
-
|
|
1625
|
+
The transmits, in order. Each one must have the same source coordinates
|
|
1626
|
+
and the same signal kinds as the Solver's source. The signals may differ in
|
|
1627
|
+
length and are padded with trailing zeros to the longest drive; with
|
|
1628
|
+
``use_ultra=True`` they must have one length.
|
|
1546
1629
|
The Solver's ``source_type`` applies to every transmit.
|
|
1547
1630
|
simulation_dir_name : str | Path
|
|
1548
1631
|
The directory name of the simulation under the work directory.
|
|
@@ -1604,7 +1687,7 @@ class Solver:
|
|
|
1604
1687
|
highpass_cutoff_mhz=highpass_cutoff_mhz,
|
|
1605
1688
|
bandpass_cutoff_mhz=bandpass_cutoff_mhz,
|
|
1606
1689
|
)
|
|
1607
|
-
members = self._batch_members(sources)
|
|
1690
|
+
members = self._batch_members(self._pad_to_longest_drive(sources))
|
|
1608
1691
|
# The sensor and the sizing below need the extended grid and medium.
|
|
1609
1692
|
self.pml_builder.run(use_pml=self.use_pml)
|
|
1610
1693
|
sensor = self._output_sensor(
|
|
@@ -1647,6 +1730,43 @@ class Solver:
|
|
|
1647
1730
|
return [path for run_paths in outputs for path in run_paths]
|
|
1648
1731
|
return np.concatenate(outputs, axis=0)
|
|
1649
1732
|
|
|
1733
|
+
def _pad_to_longest_drive(self, sources: Sequence[fullwave.Source]) -> list[fullwave.Source]:
|
|
1734
|
+
"""Return the transmits with every signal padded with zeros to the longest drive.
|
|
1735
|
+
|
|
1736
|
+
The longest drive is the longest signal among the transmits and the Solver's
|
|
1737
|
+
source. The transmits are copied; the given sources stay as they are.
|
|
1738
|
+
|
|
1739
|
+
Parameters
|
|
1740
|
+
----------
|
|
1741
|
+
sources : Sequence[fullwave.Source]
|
|
1742
|
+
The transmits, in order.
|
|
1743
|
+
|
|
1744
|
+
Returns
|
|
1745
|
+
-------
|
|
1746
|
+
list[fullwave.Source]
|
|
1747
|
+
The transmits, with signals of equal length.
|
|
1748
|
+
|
|
1749
|
+
"""
|
|
1750
|
+
names = ("p0", "p0_additive", "u0", "v0", "w0")
|
|
1751
|
+
reference = [self.source] if getattr(self, "source", None) is not None else []
|
|
1752
|
+
padded = []
|
|
1753
|
+
for source in sources:
|
|
1754
|
+
member = copy.copy(source)
|
|
1755
|
+
for name in names:
|
|
1756
|
+
signal = getattr(member, name, None)
|
|
1757
|
+
if signal is None:
|
|
1758
|
+
continue
|
|
1759
|
+
length = max(
|
|
1760
|
+
np.shape(getattr(other, name, None))[-1]
|
|
1761
|
+
for other in [*sources, *reference]
|
|
1762
|
+
if getattr(other, name, None) is not None
|
|
1763
|
+
)
|
|
1764
|
+
if np.shape(signal)[-1] < length:
|
|
1765
|
+
pad = [(0, 0)] * (np.ndim(signal) - 1) + [(0, length - np.shape(signal)[-1])]
|
|
1766
|
+
setattr(member, name, np.pad(signal, pad))
|
|
1767
|
+
padded.append(member)
|
|
1768
|
+
return padded
|
|
1769
|
+
|
|
1650
1770
|
def _batch_members(self, sources: Sequence[fullwave.Source]) -> list[fullwave.Source]:
|
|
1651
1771
|
"""Check the transmits of a batch against the Solver's source and return them.
|
|
1652
1772
|
|
|
@@ -1730,7 +1850,10 @@ class Solver:
|
|
|
1730
1850
|
return f"the {kind} signal is present in only one of them"
|
|
1731
1851
|
if expected is None:
|
|
1732
1852
|
continue
|
|
1733
|
-
if
|
|
1853
|
+
if (
|
|
1854
|
+
np.shape(found)[:-1] != np.shape(expected)[:-1]
|
|
1855
|
+
or np.shape(found)[-1] < np.shape(expected)[-1]
|
|
1856
|
+
):
|
|
1734
1857
|
return f"the {kind} signal shape {np.shape(found)} != {np.shape(expected)}"
|
|
1735
1858
|
expected_coords = getattr(self.source, coords, None)
|
|
1736
1859
|
found_coords = getattr(member, coords, None)
|