Coverage for kwave/kspaceFirstOrderAS.py: 11%

1 import tempfile

2

3 from kwave.executor import Executor

4 from kwave.kWaveSimulation_helper import retract_transducer_grid_size, save_to_disk_func

5 from kwave.kspaceFirstOrder import *

6 from kwave.kWaveSimulation import kWaveSimulation

7 from kwave.utils import *

8 from kwave.enums import DiscreteCosine

9

10

11@kspaceFirstOrderC()

12 def 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

62 def 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)