Coverage for kwave/options.py: 16%
183 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
1from dataclasses import dataclass
2import numpy as np
3from kwave.utils import get_h5_literals
6@dataclass
7class SimulationOptions(object):
8 # =========================================================================
9 # FLAGS WHICH CAN BE CONTROLLED WITH OPTIONAL INPUTS (THESE CAN BE MODIFIED)
10 # =========================================================================
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
28 # =========================================================================
29 # VARIABLES THAT CAN BE CHANGED USING OPTIONAL INPUTS (THESE CAN BE MODIFIED)
30 # =========================================================================
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
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
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
49 @staticmethod
50 def init(kgrid, elastic_code: bool, axisymmetric: bool, **kwargs):
51 """
52 Initialize the Simulation Options
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:
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
80 Returns:
81 SimulationOptions instance
82 """
83 # =========================================================================
84 # FIXED LITERALS USED IN THE CODE (THESE CAN BE MODIFIED)
85 # =========================================================================
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.
91 # general
92 STREAM_TO_DISK_STEPS_DEF = 200 # number of steps before streaming to disk
94 # filenames
95 SAVE_TO_DISK_FILENAME_DEF = 'kwave_input_data.h5'
97 options = SimulationOptions()
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]
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''."
133 # replace double with off
134 if data_cast == 'double':
135 data_cast = 'off'
137 # create empty string to hold extra cast variable for use with the parallel computing toolbox
138 data_cast_prepend = ''
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'
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
152 elif key == 'DataRecast':
153 options.data_recast = val
154 assert isinstance(val, bool), "Optional input ''DataRecast'' must be Boolean."
156 elif key == 'HDFCompressionLevel':
157 # assign input
158 hdf_compression_level = val
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
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
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.")
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]
191 elif key == 'PMLInside':
192 assert isinstance(val, bool), "Optional input ''PMLInside'' must be Boolean."
193 options.pml_inside = val
195 elif key == 'PMLRange':
196 options.pml_search_range = val
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.")
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]))
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
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."
236 assert np.isscalar(val) or isinstance(val, bool), \
237 "Optional input ''StreamToDisk'' must be a single scalar or Boolean value."
239 options.stream_to_disk = val
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
245 elif key == 'SaveToDisk':
246 assert kgrid.dim != 1, "Optional input ''SaveToDisk'' is not compatible with 1D simulations."
248 assert isinstance(val, (bool, str)), "Optional input ''SaveToDisk'' must be Boolean or a String."
249 options.save_to_disk = val
251 if isinstance(options.save_to_disk, bool) and options.save_to_disk:
252 options.save_to_disk = SAVE_TO_DISK_FILENAME_DEF
254 elif key == 'SaveToDiskExit':
255 assert kgrid.dim != 1, "Optional input ''SaveToDisk'' is not compatible with 1D simulations."
257 assert isinstance(val, bool), "Optional input ''SaveToDiskExit'' must be Boolean."
258 options.save_to_disk_exit = val
260 elif key == 'ScaleSourceTerms':
261 assert isinstance(val, bool), "Optional input ''ScaleSourceTerms'' must be Boolean."
262 options.scale_source_terms = val
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]
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."
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
279 elif key == 'UsekSpace':
280 assert isinstance(val, bool), "Optional input ''UsekSpace'' must be Boolean."
281 options.use_kspace = val
283 elif key == 'UseSG':
284 assert isinstance(val, bool), "Optional input ''UseSG'' must be Boolean."
285 options.use_sg = val
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.')
291 else:
292 raise NotImplementedError(f"Unknown optional input: {key}.")
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)
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])
316 # cleanup unused variables
317 del pml_size_temp
318 return options