Coverage for kwave/kspaceFirstOrderAS.py: 11%

87 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-10-24 11:55 -0700

1import tempfile 

2 

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 

9 

10 

11@kspaceFirstOrderC() 

12def kspaceFirstOrderASC(**kwargs): 

13 """ 

14 Axisymmetric time-domain simulation of wave propagation using C++ code. 

15 

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. 

21 

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. 

30 

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. 

39 

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'. 

47 

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. 

51 

52 Args: 

53 **kwargs: 

54 

55 Returns: 

56 """ 

57 # generate the input file and save to disk 

58 kspaceFirstOrderAS(**kwargs) 

59 return kwargs['SaveToDisk'] 

60 

61 

62def kspaceFirstOrderAS(kgrid, medium, source, sensor, **kwargs): 

63 """ 

64 Axisymmetric time-domain simulation of wave propagation. 

65 

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. 

76 

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. 

91 

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. 

108 

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. 

114 

115 Args: 

116 kgrid: kWaveGrid instance 

117 medium: kWaveMedium instance 

118 source: kWaveSource instance 

119 sensor: kWaveSensor instance 

120 **kwargs: 

121 

122 Returns: 

123 

124 """ 

125 # start the timer and store the start time 

126 TicToc.tic() 

127 

128 k_sim = kWaveSimulation(kgrid, medium, source, sensor, **kwargs) 

129 k_sim.input_checking('kspaceFirstOrderAS') 

130 

131 # ========================================================================= 

132 # CALCULATE MEDIUM PROPERTIES ON STAGGERED GRID 

133 # ========================================================================= 

134 options = k_sim.options 

135 

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) 

138 

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 

149 

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 

153 

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 

159 

160 # ========================================================================= 

161 # PREPARE DERIVATIVE AND PML OPERATORS 

162 # ========================================================================= 

163 

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 

171 

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) 

176 

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 

182 

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 

206 

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) 

219 

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) 

226 

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 

230 

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])) 

235 

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) 

240 

241 # cleanup unused variables 

242 del k_dtt 

243 

244 else: 

245 k_sim.kappa = 1 

246 k_sim.source_kappa = 1 

247 

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 

251 

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.' 

255 

256 # ========================================================================= 

257 # SAVE DATA TO DISK FOR RUNNING SIMULATION EXTERNAL TO MATLAB 

258 # ========================================================================= 

259 

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]] 

265 

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, 

289 

290 'source_ux': k_sim.source_ux, 

291 'source_uy': k_sim.source_uy, 

292 'source_uz': k_sim.source_uz, 

293 

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, 

300 

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 })) 

307 

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) 

310 

311 # exit matlab computation if required 

312 if options.save_to_disk_exit: 

313 return 

314 

315 input_filename = k_sim.options.save_to_disk 

316 output_filename = os.path.join(tempfile.gettempdir(), 'output.h5') 

317 

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)