Coverage for kwave/options.py: 16%

183 statements  

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

1from dataclasses import dataclass 

2import numpy as np 

3from kwave.utils import get_h5_literals 

4 

5 

6@dataclass 

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