Coverage for kwave/kspaceFirstOrderAS.py: 11%
87 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-24 11:55 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-24 11:55 -0700
1import tempfile
3from kwave.executor import Executor
4from kwave.kWaveSimulation_helper import retract_transducer_grid_size, save_to_disk_func
5from kwave.kspaceFirstOrder import *
6from kwave.kWaveSimulation import kWaveSimulation
7from kwave.utils import *
8from kwave.enums import DiscreteCosine
11@kspaceFirstOrderC()
12def kspaceFirstOrderASC(**kwargs):
13 """
14 Axisymmetric time-domain simulation of wave propagation using C++ code.
16 kspaceFirstOrderASC provides a blind interface to the C++ version of
17 kspaceFirstOrderAS (called kspaceFirstOrder-OMP) in the same way as
18 kspaceFirstOrder3DC. Note, the C++ code does not support all input
19 options, and all display options are ignored (only command line
20 outputs are given). See the k-Wave user manual for more information.
22 The function works by appending the optional input 'SaveToDisk' to
23 the user inputs and then calling kspaceFirstOrderAS to save the input
24 files to disk. The contents of sensor.record (if set) are parsed as
25 input flags, and the C++ code is run using the system command. The
26 output files are then automatically loaded from disk and returned in
27 the same fashion as kspaceFirstOrderAS. The input and output files
28 are saved to the temporary directory native to the operating system,
29 and are deleted after the function runs.
31 For small simulations, running the simulation on a smaller number of
32 cores can improve performance as the matrices are often small enough
33 to fit within cache. It is recommended to adjust the value of
34 'NumThreads' to optimise performance for a given simulation size and
35 computer hardware. By default, simulations smaller than 128^2 are
36 set to run using a single thread (this behaviour can be over-ridden
37 using the 'NumThreads' option). In some circumstances, for very small
38 simulations, the C++ code can be slower than the MATLAB code.
40 This function requires the C++ binary/executable of
41 kspaceFirstOrder-OMP to be downloaded from
42 http://www.k-wave.org/download.php and placed in the "binaries"
43 directory of the k-Wave toolbox (the same binary is used for
44 simulations in 2D, 3D, and axisymmetric coordinates). Alternatively,
45 the name and location of the binary can be specified using the
46 optional input parameters 'BinaryName' and 'BinariesPath'.
48 This function is essentially a wrapper and directly uses the capabilities
49 of kspaceFirstOrder3DC by replacing the binary name with the name of the
50 GPU binary.
52 Args:
53 **kwargs:
55 Returns:
56 """
57 # generate the input file and save to disk
58 kspaceFirstOrderAS(**kwargs)
59 return kwargs['SaveToDisk']
62def kspaceFirstOrderAS(kgrid, medium, source, sensor, **kwargs):
63 """
64 Axisymmetric time-domain simulation of wave propagation.
66 kspaceFirstOrderAS simulates the time-domain propagation of
67 compressional waves through an axisymmetric homogeneous or
68 heterogeneous acoustic medium. The code is functionally very similar
69 to kspaceFirstOrder2D. However, a 2D axisymmetric coordinate system
70 is used instead of a 2D Cartesian coordinate system. In this case, x
71 corresponds to the axial dimension, and y corresponds to the radial
72 dimension. In the radial dimension, the first grid point corresponds
73 to the grid origin, i.e., y = 0. In comparison, for
74 kspaceFirstOrder2D, the Cartesian point y = 0 is in the middle of the
75 computational grid.
77 The input structures kgrid, medium, source, and sensor are defined in
78 exactly the same way as for kspaceFirstOrder2D. However,
79 computationally, there are several key differences. First, the
80 axisymmetric code solves the coupled first-order equations accounting
81 for viscous absorption (not power law), so only medium.alpha_power =
82 2 is supported. This value is set by default, and doesn't need to be
83 defined. This also means that medium.alpha_mode and
84 medium.alpha_filter are not supported. Second, for a homogeneous
85 medium, the k-space correction used to counteract the numerical
86 dispersion introduced by the finite-difference time step is not exact
87 (as it is for the other fluid codes). However, the approximate
88 k-space correction still works very effectively, so dispersion errors
89 should still be small. See kspaceFirstOrder2D for additional details
90 on the function inputs.
92 In the x-dimension (axial), the FFT is used to compute spatial
93 gradients. In the y-dimension (radial), two choices of symmetry are
94 possible. These are whole-sample-symmetric on the interior radial
95 boundary (y = 0) and either whole-sample-symmetric or
96 whole-sample-asymmetric on the exterior radial boundary. These are
97 abbreviated WSWA and WSWS. The WSWA and WSWS symmetries are
98 implemented using both discrete trigonometric transforms (DTTs), and
99 via the FFT by manually mirroring the domain. The latter options are
100 abbreviated as WSWA-FFT and WSWS-FFT. The WSWA/WSWS options and the
101 corresponding WSWA-FFT/WSWS-FFT options agree to machine precision.
102 When using the PML, the choice of symmetry doesn't matter, and all
103 options give very similar results (to several decimal places).
104 Computationally, the DTT implementations are more efficient, but
105 require additional compiled MATLAB functions (not currently part of
106 k-Wave). The symmetry can be set by using the optional input
107 'RadialSymmetry'. The WSWA-FFT symmetry is set by default.
109 Note: For heterogeneous medium parameters, medium.sound_speed and
110 medium.density must be given in matrix form with the same dimensions as
111 kgrid. For homogeneous medium parameters, these can be given as single
112 numeric values. If the medium is homogeneous and velocity inputs or
113 outputs are not required, it is not necessary to specify medium.density.
115 Args:
116 kgrid: kWaveGrid instance
117 medium: kWaveMedium instance
118 source: kWaveSource instance
119 sensor: kWaveSensor instance
120 **kwargs:
122 Returns:
124 """
125 # start the timer and store the start time
126 TicToc.tic()
128 k_sim = kWaveSimulation(kgrid, medium, source, sensor, **kwargs)
129 k_sim.input_checking('kspaceFirstOrderAS')
131 # =========================================================================
132 # CALCULATE MEDIUM PROPERTIES ON STAGGERED GRID
133 # =========================================================================
134 options = k_sim.options
136 # interpolate the values of the density at the staggered grid locations
137 # where sgx = (x + dx/2, y, z), sgy = (x, y + dy/2, z), sgz = (x, y, z + dz/2)
139 k_sim.rho0 = np.atleast_1d(k_sim.rho0)
140 if num_dim2(k_sim.rho0) == 2 and options.use_sg:
141 # rho0 is heterogeneous and staggered grids are used
142 grid_points = [k_sim.kgrid.x, k_sim.kgrid.y]
143 k_sim.rho0_sgx = interpolate2D(grid_points, k_sim.rho0, [k_sim.kgrid.x + k_sim.kgrid.dx / 2, k_sim.kgrid.y])
144 k_sim.rho0_sgy = interpolate2D(grid_points, k_sim.rho0, [k_sim.kgrid.x, k_sim.kgrid.y + k_sim.kgrid.dy / 2])
145 else:
146 # rho0 is homogeneous or staggered grids are not used
147 k_sim.rho0_sgx = k_sim.rho0
148 k_sim.rho0_sgy = k_sim.rho0
150 # invert rho0 so it doesn't have to be done each time step
151 k_sim.rho0_sgx_inv = 1 / k_sim.rho0_sgx
152 k_sim.rho0_sgy_inv = 1 / k_sim.rho0_sgy
154 # clear unused variables if not using them in _saveToDisk
155 if not options.save_to_disk:
156 del k_sim.rho0_sgx
157 del k_sim.rho0_sgy
158 k_sim.rho0_sgz = None
160 # =========================================================================
161 # PREPARE DERIVATIVE AND PML OPERATORS
162 # =========================================================================
164 # get the PML operators based on the reference sound speed and PML settings
165 Nx, Ny = k_sim.kgrid.Nx, k_sim.kgrid.Ny
166 dx, dy = k_sim.kgrid.dx, k_sim.kgrid.dy
167 dt = k_sim.kgrid.dt
168 pml_x_alpha, pml_y_alpha = options.pml_x_alpha, options.pml_y_alpha
169 pml_x_size, pml_y_size = options.pml_x_size, options.pml_y_size
170 c_ref = k_sim.c_ref
172 k_sim.pml_x = get_pml(Nx, dx, dt, c_ref, pml_x_size, pml_x_alpha, False, 1, False)
173 k_sim.pml_x_sgx = get_pml(Nx, dx, dt, c_ref, pml_x_size, pml_x_alpha, True and options.use_sg, 1, False)
174 k_sim.pml_y = get_pml(Ny, dy, dt, c_ref, pml_y_size, pml_y_alpha, False, 2, True)
175 k_sim.pml_y_sgy = get_pml(Ny, dy, dt, c_ref, pml_y_size, pml_y_alpha, True and options.use_sg, 2, True)
177 # define the k-space, derivative, and shift operators
178 # for the x (axial) direction, the operators are the same as normal
179 kx_vec = k_sim.kgrid.k_vec.x
180 k_sim.ddx_k_shift_pos = ifftshift( 1j * kx_vec * np.exp( 1j * kx_vec * dx/2) ).T
181 k_sim.ddx_k_shift_neg = ifftshift( 1j * kx_vec * np.exp(-1j * kx_vec * dx/2) ).T
183 # for the y (radial) direction
184 # when using DTTs:
185 # - there is no explicit grid shift (this is done by choosing DTTs
186 # with the appropriate symmetry)
187 # - ifftshift isn't required as the wavenumbers start from DC
188 # when using FFTs:
189 # - the grid is expanded, and the fields replicated in the radial
190 # dimension to give the required symmetry
191 # - the derivative and shift operators are defined as normal
192 if options.radial_symmetry in ['WSWA-FFT', 'WSWS-FFT']:
193 # create a new kWave grid object with expanded radial grid
194 if options.radial_symmetry == 'WSWA-FFT':
195 # extend grid by a factor of x4 to account for
196 # symmetries in WSWA
197 kgrid_exp = kWaveGrid([Nx, Ny * 4], [dx, dy])
198 elif options.radial_symmetry == 'WSWS-FFT':
199 # extend grid by a factor of x2 - 2 to account for
200 # symmetries in WSWS
201 kgrid_exp = kWaveGrid([Nx, Ny * 2 - 2], [dx, dy])
202 # define operators, rotating y-direction for use with bsxfun
203 k_sim.ddy_k = ifftshift( 1j * options.k_vec.y ).T
204 k_sim.y_shift_pos = ifftshift( np.exp( 1j * kgrid_exp.k_vec.y * kgrid_exp.dy/2) ).T
205 k_sim.y_shift_neg = ifftshift( np.exp(-1j * kgrid_exp.k_vec.y * kgrid_exp.dy/2) ).T
207 # define the k-space operator
208 if options.use_kspace:
209 k_sim.kappa = ifftshift(sinc(c_ref * kgrid_exp.k * dt / 2))
210 if (k_sim.source_p and (k_sim.source.p_mode == 'additive')) or ((k_sim.source_ux or k_sim.source_uy) and (k_sim.source.u_mode == 'additive')):
211 k_sim.source_kappa = ifftshift(np.cos (c_ref * kgrid_exp.k * dt / 2))
212 else:
213 k_sim.kappa = 1
214 k_sim.source_kappa = 1
215 elif options.radial_symmetry in ['WSWA', 'WSWS']:
216 if options.radial_symmetry == 'WSWA':
217 # get the wavenumbers and implied length for the DTTs
218 ky_vec, M = k_sim.kgrid.ky_vec_dtt(DiscreteCosine.TYPE_3)
220 # define the derivative operators
221 k_sim.ddy_k_wswa = -ky_vec.T
222 k_sim.ddy_k_hahs = ky_vec.T
223 elif options.radial_symmetry == 'WSWS':
224 # get the wavenumbers and implied length for the DTTs
225 ky_vec, M = k_sim.kgrid.ky_vec_dtt(DiscreteCosine.TYPE_1)
227 # define the derivative operators
228 k_sim.ddy_k_wsws = -ky_vec[1:].T
229 k_sim.ddy_k_haha = ky_vec[1:].T
231 # define the k-space operator
232 if options.use_kspace:
233 # define scalar wavenumber
234 k_dtt = np.sqrt(np.tile(ifftshift(k_sim.kgrid.k_vec.x)**2, [1, k_sim.kgrid.Ny]) + np.tile((ky_vec.T)**2, [k_sim.kgrid.Nx, 1]))
236 # define k-space operators
237 k_sim.kappa = sinc(c_ref * k_dtt * k_sim.kgrid.dt / 2)
238 if (k_sim.source_p and (k_sim.source.p_mode == 'additive')) or ((k_sim.source_ux or k_sim.source_uy) and (k_sim.source.u_mode == 'additive')):
239 k_sim.source_kappa = np.cos(c_ref * k_dtt * k_sim.kgrid.dt / 2)
241 # cleanup unused variables
242 del k_dtt
244 else:
245 k_sim.kappa = 1
246 k_sim.source_kappa = 1
248 # define staggered and non-staggered grid axial distance
249 k_sim.y_vec = (k_sim.kgrid.y_vec - k_sim.kgrid.y_vec[0]).T
250 k_sim.y_vec_sg = (k_sim.kgrid.y_vec - k_sim.kgrid.y_vec[0] + k_sim.kgrid.dy/2).T
252 # option to run simulations without the spatial staggered grid is not
253 # supported for the axisymmetric code
254 assert options.use_sg, 'Optional input ''UseSG'' is not supported for axisymmetric simulations.'
256 # =========================================================================
257 # SAVE DATA TO DISK FOR RUNNING SIMULATION EXTERNAL TO MATLAB
258 # =========================================================================
260 # save to disk option for saving the input matrices to disk for running
261 # simulations using k-Wave++
262 if options.save_to_disk:
263 # store the pml size for resizing transducer object below
264 retract_size = [[options.pml_x_size, options.pml_y_size, options.pml_z_size]]
266 # run subscript to save files to disk
267 save_to_disk_func(k_sim.kgrid, k_sim.medium, k_sim.source, k_sim.options,
268 dotdict({
269 'ddx_k_shift_pos': k_sim.ddx_k_shift_pos,
270 'ddx_k_shift_neg': k_sim.ddx_k_shift_neg,
271 'dt': k_sim.dt,
272 'c0': k_sim.c0,
273 'c_ref': k_sim.c_ref,
274 'rho0': k_sim.rho0,
275 'rho0_sgx': k_sim.rho0_sgx,
276 'rho0_sgy': k_sim.rho0_sgy,
277 'rho0_sgz': k_sim.rho0_sgz,
278 'p_source_pos_index': k_sim.p_source_pos_index,
279 'u_source_pos_index': k_sim.u_source_pos_index,
280 's_source_pos_index': k_sim.s_source_pos_index,
281 'transducer_input_signal': k_sim.transducer_input_signal,
282 'delay_mask': k_sim.delay_mask,
283 'sensor_mask_index': k_sim.sensor_mask_index,
284 'record': k_sim.record,
285 }),
286 dotdict({
287 'source_p': k_sim.source_p,
288 'source_p0': k_sim.source_p0,
290 'source_ux': k_sim.source_ux,
291 'source_uy': k_sim.source_uy,
292 'source_uz': k_sim.source_uz,
294 'source_sxx': k_sim.source_sxx,
295 'source_syy': k_sim.source_syy,
296 'source_szz': k_sim.source_szz,
297 'source_sxy': k_sim.source_sxy,
298 'source_sxz': k_sim.source_sxz,
299 'source_syz': k_sim.source_syz,
301 'transducer_source': k_sim.transducer_source,
302 'nonuniform_grid': k_sim.nonuniform_grid,
303 'elastic_code': k_sim.elastic_code,
304 'axisymmetric': k_sim.axisymmetric,
305 'cuboid_corners': k_sim.cuboid_corners,
306 }))
308 # run subscript to resize the transducer object if the grid has been expanded
309 retract_transducer_grid_size(k_sim.source, k_sim.sensor, retract_size, k_sim.options.pml_inside)
311 # exit matlab computation if required
312 if options.save_to_disk_exit:
313 return
315 input_filename = k_sim.options.save_to_disk
316 output_filename = os.path.join(tempfile.gettempdir(), 'output.h5')
318 executor = Executor(device='gpu')
319 sensor_data = executor.run_simulation(input_filename, output_filename, options='--p_raw')
320 return k_sim.sensor.combine_sensor_data(sensor_data)