Coverage for kwave/options.py: 16%

1 from dataclasses import dataclass

2 import numpy as np

3 from kwave.utils import get_h5_literals

4

5

6@dataclass

7 class SimulationOptions(object):

8 # =========================================================================

9 # FLAGS WHICH CAN BE CONTROLLED WITH OPTIONAL INPUTS (THESE CAN BE MODIFIED)

10 # =========================================================================

11

12 # flags which control the behaviour of the simulations

13 pml_inside = True #: put the PML inside the grid defined by the user

14 save_to_disk = False #: save the input data to a HDF5 file

15 save_to_disk_exit = True #: exit the simulation after saving the HDF5 file

16 scale_source_terms = True #: apply the source scaling term to time varying sources

17 smooth_c0 = False #: smooth the sound speed distribution

18 smooth_rho0 = False #: smooth the density distribution

19 smooth_p0 = True #: smooth the initial pressure distribution

20 use_kspace = True #: use the k-space correction

21 use_sg = True #: use a staggered grid

22 pml_auto = False #: automatically choose the PML size to give small prime factors

23 create_log = False #: create a log using diary

24 use_finite_difference = False #: use finite difference gradients instead of spectral (in 1D)

25 stream_to_disk = False #: buffer the sensor data to disk (in 3D)

26 data_recast = False #: recast the sensor data back to double precision

27

28 # =========================================================================

29 # VARIABLES THAT CAN BE CHANGED USING OPTIONAL INPUTS (THESE CAN BE MODIFIED)

30 # =========================================================================

31

32 # general settings

33 cartesian_interp = 'linear' #: interpolation mode for Cartesian sensor mask

34 hdf_compression_level = None #: zip compression level for HDF5 input files

35 data_cast = 'off' #: data cast

36 pml_search_range = [10, 40] #: search range used when automatically determining PML size

37 radial_symmetry = 'WSWA-FFT' #: radial symmetry used in axisymmetric code

38 multi_axial_PML_ratio = 0.1 #: MPML settings

39

40 # default PML properties and plot scale

41 pml_x_alpha, pml_y_alpha, pml_z_alpha = None, None, None #: PML Alpha

42 pml_x_size, pml_y_size, pml_z_size = None, None, None #: PML Size

43

44 def __post_init__(self):

45 # load the HDF5 literals (for the default compression level)

46 h5_literals = get_h5_literals()

47 self.hdf_compression_level = h5_literals.HDF_COMPRESSION_LEVEL

48

49 @staticmethod

50 def init(kgrid, elastic_code: bool, axisymmetric: bool, **kwargs):

51 """

52 Initialize the Simulation Options

53

54 Args:

55 kgrid: kWaveGrid instance

56 elastic_code: Flag that indicates whether elastic simulation is used

57 axisymmetric: Flag that indicates whether axisymmetric simulation is used

58 **kwargs: Dictionary that holds following optional simulation properties:

59

60 * CartInterp: Interpolation mode used to extract the pressure when a Cartesian sensor mask is given. If set to 'nearest' and more than one Cartesian point maps to the same grid point, duplicated data points are discarded and sensor_data will be returned with less points than that specified by sensor.mask (default = 'linear').

61 * CreateLog: Boolean controlling whether the command line output is saved using the diary function with a date and time stamped filename (default = false).

62 * DataCast: String input of the data type that variables are cast to before computation. For example, setting to 'single' will speed up the computation time (due to the improved efficiency of fftn and ifftn for this data type) at the expense of a loss in precision. This variable is also useful for utilising GPU parallelisation through libraries such as the Parallel Computing Toolbox by setting 'DataCast' to 'gpuArray-single' (default = 'off').

63 * DataRecast: Boolean controlling whether the output data is cast back to double precision. If set to false, sensor_data will be returned in the data format set using the 'DataCast' option.

64 * HDFCompressionLevel: Compression level used for writing the input HDF5 file when using 'SaveToDisk' or kspaceFirstOrder3DC. Can be set to an integer between 0 (no compression, the default) and 9 (maximum compression). The compression is lossless. Increasing the compression level will reduce the file size if there are portions of the medium that are homogeneous, but will also increase the time to create the HDF5 file.

65 * MultiAxialPMLRatio: MPML settings

66 * PMLAlpha: Absorption within the perfectly matched layer in Nepers per grid point (default = 2).

67 * PMLInside: Boolean controlling whether the perfectly matched layer is inside or outside the grid. If set to false, the input grids are enlarged by PMLSize before running the simulation (default = true).

68 * PMLRange: Search range used when automatically determining PML size. Tuple of two elements

69 * PMLSize: Size of the perfectly matched layer in grid points. By default, the PML is added evenly to all sides of the grid, however, both PMLSize and PMLAlpha can be given as three element arrays to specify the x, y, and z properties, respectively. To remove the PML, set the appropriate PMLAlpha to zero rather than forcing the PML to be of zero size (default = 10).

70 * RadialSymmetry: Radial symmetry used in axisymmetric code

71 * StreamToDisk: Boolean controlling whether sensor_data is periodically saved to disk to avoid storing the complete matrix in memory. StreamToDisk may also be given as an integer which specifies the number of times steps that are taken before the data is saved to disk (default = 200).

72 * SaveToDisk: String containing a filename (including pathname if required). If set, after the precomputation phase, the input variables used in the time loop are saved the specified location in HDF5 format. The simulation then exits. The saved variables can be used to run simulations using the C++ code.

73 * SaveToDiskExit: Exit the simulation after saving the HDF5 file

74 * ScaleSourceTerms: Apply the source scaling term to time varying sources

75 * Smooth: Boolean controlling whether source.p0, medium.sound_speed, and medium.density are smoothed using smooth before computation. 'Smooth' can either be given as a single Boolean value or as a 3 element array to control the smoothing of source.p0, medium.sound_speed, and medium.density, independently (default = [true, false, false]).

76 * UseFD: Use finite difference gradients instead of spectral (in 1D)

77 * UsekSpace: use the k-space correction

78 * UseSG: Use a staggered grid

79

80 Returns:

81 SimulationOptions instance

82 """

83 # =========================================================================

84 # FIXED LITERALS USED IN THE CODE (THESE CAN BE MODIFIED)

85 # =========================================================================

86

87 # Literals used to set default parameters end with _DEF. These are cleared

88 # at the end of kspaceFirstOrder_inputChecking. Literals used at other

89 # places in the code are not cleared.

90

91 # general

92 STREAM_TO_DISK_STEPS_DEF = 200 # number of steps before streaming to disk

93

94 # filenames

95 SAVE_TO_DISK_FILENAME_DEF = 'kwave_input_data.h5'

96

97 options = SimulationOptions()

98

99 if kgrid.dim == 1:

100 options.pml_x_alpha = 2

101 options.pml_x_size = 20

102 options.plot_scale = [-1.1, 1.1]

103 elif kgrid.dim == 2:

104 options.pml_x_alpha = 2

105 options.pml_y_alpha = options.pml_x_alpha

106 options.pml_x_size = 20

107 options.pml_y_size = options.pml_x_size

108 options.plot_scale = [-1, 1]

109 elif kgrid.dim == 3:

110 options.pml_x_alpha = 2

111 options.pml_y_alpha = options.pml_x_alpha

112 options.pml_z_alpha = options.pml_x_alpha

113 options.pml_x_size = 10

114 options.pml_y_size = options.pml_x_size

115 options.pml_z_size = options.pml_x_size

116 options.plot_scale = [-1, 1]

117

118 # replace defaults with user defined values if provided and check inputs

119 for key, val in kwargs.items():

120 if key == 'CartInterp':

121 assert val in ['linear', 'nearest'], \

122 "Optional input ''CartInterp'' must be set to ''linear'' or ''nearest''."

123 elif key == 'CreateLog':

124 # assign input

125 options.create_log = val

126 assert isinstance(val, bool), "Optional input ''CreateLog'' must be Boolean."

127 elif key == 'DataCast':

128 data_cast = val

129 assert isinstance(data_cast, str), "Optional input ''DataCast'' must be a string."

130 assert data_cast in ['off', 'double', 'single', 'gpuArray-single', 'gpuArray-double'], \

131 "Invalid input for ''DataCast''."

132

133 # replace double with off

134 if data_cast == 'double':

135 data_cast = 'off'

136

137 # create empty string to hold extra cast variable for use with the parallel computing toolbox

138 data_cast_prepend = ''

139

140 # replace PCT options with gpuArray

141 if data_cast == 'gpuArray-single':

142 data_cast = 'gpuArray'

143 data_cast_prepend = 'single'

144 elif data_cast == 'gpuArray-double':

145 data_cast = 'gpuArray'

146

147 if data_cast == 'gpuArray':

148 raise NotImplementedError("gpuArray is not supported in Python-version")

149 options.data_cast = data_cast

150 options.data_cast_prepend = data_cast_prepend

151

152 elif key == 'DataRecast':

153 options.data_recast = val

154 assert isinstance(val, bool), "Optional input ''DataRecast'' must be Boolean."

155

156 elif key == 'HDFCompressionLevel':

157 # assign input

158 hdf_compression_level = val

159

160 # check value is an integer between 0 and 9

161 assert isinstance(hdf_compression_level, int) and 0 <= hdf_compression_level <= 9, \

162 "Optional input ''HDFCompressionLevel'' must be an integer between 0 and 9."

163 options.hdf_compression_level = hdf_compression_level

164

165 elif key == 'MultiAxialPMLRatio':

166 assert np.isscalar(val) and val >= 0, \

167 "Optional input ''MultiAxialPMLRatio'' must be a single positive value."

168 # assign input

169 options.multi_axial_PML_ratio = val

170

171 elif key == 'PMLAlpha':

172 # check input is correct size

173 val = np.atleast_1d(val)

174 if val.size > kgrid.dim:

175 if kgrid.dim > 1:

176 raise ValueError(f"Optional input ''PMLAlpha'' must be a 1 or {kgrid.dim} element numerical array.")

177 else:

178 raise ValueError(f"Optional input ''PMLAlpha'' must be a single numerical value.")

179

180 # assign input based on number of dimensions

181 if kgrid.dim == 1:

182 options.pml_x_alpha = val

183 elif kgrid.dim == 2:

184 options.pml_x_alpha = val[0]

185 options.pml_y_alpha = val[-1]

186 elif kgrid.dim == 2:

187 options.pml_x_alpha = val[0]

188 options.pml_y_alpha = val[len(val) // 2]

189 options.pml_z_alpha = val[-1]

190

191 elif key == 'PMLInside':

192 assert isinstance(val, bool), "Optional input ''PMLInside'' must be Boolean."

193 options.pml_inside = val

194

195 elif key == 'PMLRange':

196 options.pml_search_range = val

197

198 elif key == 'PMLSize':

199 if isinstance(val, str):

200 # check for 'auto'

201 if val == 'auto':

202 options.pml_auto = True

203 else:

204 raise ValueError(f"Optional input ''PMLSize'' must be a 1 or {kgrid.dim} element "

205 f"numerical array, or set to ''auto''.")

206 else:

207 val = np.atleast_1d(val)

208 if len(val) > kgrid.dim:

209 if kgrid.dim > 1:

210 raise ValueError(f"Optional input ''PMLSize'' must be a 1 or {kgrid.dim} element numerical array.")

211 else:

212 raise ValueError(f"Optional input ''PMLSize'' must be a single numerical value.")

213

214 # assign input based on number of dimensions, rounding to

215 # the nearest integer

216 val = np.atleast_1d(np.squeeze(val))

217 if kgrid.dim == 1:

218 options.pml_x_size = float(np.round(val))

219 elif kgrid.dim == 2:

220 options.pml_x_size = float(np.round(val[0]))

221 options.pml_y_size = float(np.round(val[-1]))

222 elif kgrid.dim == 3:

223 options.pml_x_size = float(np.round(val[0]))

224 options.pml_y_size = float(np.round(val[len(val) // 2]))

225 options.pml_z_size = float(np.round(val[-1]))

226

227 elif key == 'RadialSymmetry':

228 assert val in ['WSWA', 'WSWS', 'WSWA-FFT', 'WSWS-FFT'], \

229 "Optional input ''RadialSymmetry'' must be set to ''WSWA'', ''WSWS'', ''WSWA-FFT'', ''WSWS-FFT''."

230 options.radial_symmetry = val

231

232 elif key == 'StreamToDisk':

233 assert not elastic_code and kgrid.dim == 3, \

234 "Optional input ''StreamToDisk'' is currently only compatible with 3D fluid simulations."

235

236 assert np.isscalar(val) or isinstance(val, bool), \

237 "Optional input ''StreamToDisk'' must be a single scalar or Boolean value."

238

239 options.stream_to_disk = val

240

241 # if given as a Boolean, replace with the default number of time steps

242 if isinstance(options.stream_to_disk, bool) and options.stream_to_disk:

243 options.stream_to_disk = STREAM_TO_DISK_STEPS_DEF

244

245 elif key == 'SaveToDisk':

246 assert kgrid.dim != 1, "Optional input ''SaveToDisk'' is not compatible with 1D simulations."

247

248 assert isinstance(val, (bool, str)), "Optional input ''SaveToDisk'' must be Boolean or a String."

249 options.save_to_disk = val

250

251 if isinstance(options.save_to_disk, bool) and options.save_to_disk:

252 options.save_to_disk = SAVE_TO_DISK_FILENAME_DEF

253

254 elif key == 'SaveToDiskExit':

255 assert kgrid.dim != 1, "Optional input ''SaveToDisk'' is not compatible with 1D simulations."

256

257 assert isinstance(val, bool), "Optional input ''SaveToDiskExit'' must be Boolean."

258 options.save_to_disk_exit = val

259

260 elif key == 'ScaleSourceTerms':

261 assert isinstance(val, bool), "Optional input ''ScaleSourceTerms'' must be Boolean."

262 options.scale_source_terms = val

263

264 elif key == 'Smooth':

265 val = np.atleast_1d(val)

266 assert len(val) <= 3 and np.array(val).dtype == bool, "Optional input ''Smooth'' must be a 1, 2 or 3 element Boolean array."

267 options.smooth_p0 = val[0]

268 options.smooth_c0 = val[len(val) // 2]

269 options.smooth_rho0 = val[-1]

270

271 elif key == 'UseFD':

272 # input only supported in 1D fluid code

273 assert kgrid.dim == 1 and not elastic_code, "Optional input ''UseFD'' only supported in 1D."

274

275 assert (isinstance(val, bool) and not val) or (np.issubdtype(val, np.number) and val in [2, 4]), \

276 "Optional input ''UseFD'' must be set to 2, 4, or false."

277 options.use_finite_difference = val

278

279 elif key == 'UsekSpace':

280 assert isinstance(val, bool), "Optional input ''UsekSpace'' must be Boolean."

281 options.use_kspace = val

282

283 elif key == 'UseSG':

284 assert isinstance(val, bool), "Optional input ''UseSG'' must be Boolean."

285 options.use_sg = val

286

287 elif key in ['DisplayMask', 'LogScale', 'MeshPlot', 'PlotFreq',

288 'PlotLayout', 'PlotScale', 'PlotSim', 'PlotPML']:

289 raise NotImplementedError(f'Plotting is not supported! Parameter {key} is related to plotting.')

290

291 else:

292 raise NotImplementedError(f"Unknown optional input: {key}.")

293

294 # automatically assign the PML size to give small prime factors

295 if options.pml_auto:

296 if options.pml_inside:

297 raise NotImplementedError("''PMLSize'' set to ''auto'' is only supported with ''PMLInside'' set to false.")

298 else:

299 # get optimal pml size

300 if axisymmetric:

301 pml_size_temp = getOptimalPMLSize(kgrid, options.pml_search_range, options.radial_symmetry[:4])

302 else:

303 pml_size_temp = getOptimalPMLSize(kgrid, options.pml_search_range)

304

305 # assign to individual variables

306 if kgrid.dim == 1:

307 options.pml_x_size = float(pml_size_temp[0])

308 elif kgrid.dim == 2:

309 options.pml_x_size = float(pml_size_temp[1])

310 options.pml_y_size = float(pml_size_temp[2])

311 elif kgrid.dim == 3:

312 options.pml_x_size = float(pml_size_temp[0])

313 options.pml_y_size = float(pml_size_temp[1])

314 options.pml_z_size = float(pml_size_temp[2])

315

316 # cleanup unused variables

317 del pml_size_temp

318 return options