Coverage for kwave/kmedium.py: 51%

67 statements  

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

1from dataclasses import dataclass 

2from typing import List 

3from warnings import warn 

4 

5import numpy as np 

6import kwave.utils.misc as util 

7import kwave.utils.checkutils 

8 

9 

10@dataclass 

11class kWaveMedium(object): 

12 sound_speed : np.array #: sound speed distribution within the acoustic medium [m/s] | required to be defined 

13 sound_speed_ref : np.array = None #: reference sound speed used within the k-space operator (phase correction term) [m/s] 

14 density : np.array = None #: density distribution within the acoustic medium [kg/m^3] 

15 alpha_coeff : np.array = None #: power law absorption coefficient [dB/(MHz^y cm)] 

16 alpha_power : np.array = None #: power law absorption exponent 

17 alpha_mode : np.array = None #: optional input to force either the absorption or dispersion terms in the equation of state to be excluded; valid inputs are 'no_absorption' or 'no_dispersion' 

18 alpha_filter : np.array = None #: frequency domain filter applied to the absorption and dispersion terms in the equation of state 

19 alpha_sign : np.array = None #: two element array used to control the sign of absorption and dispersion terms in the equation of state 

20 BonA : np.array = None #: parameter of nonlinearity 

21 absorbing : bool = False #: is the medium absorbing? 

22 stokes : bool = False #: is the medium absorbing stokes? 

23 

24 # """ 

25 # Note: For heterogeneous medium parameters, medium.sound_speed and 

26 # medium.density must be given in matrix form with the same dimensions as 

27 # kgrid. For homogeneous medium parameters, these can be given as single 

28 # numeric values. If the medium is homogeneous and velocity inputs or 

29 # outputs are not required, it is not necessary to specify medium.density. 

30 # """ 

31 

32 def __post_init__(self): 

33 self.sound_speed = np.atleast_1d(self.sound_speed) 

34 

35 def check_fields(self, kgrid_shape: np.ndarray) -> None: 

36 """ 

37 Check whether the given properties are valid 

38 

39 Args: 

40 kgrid_shape: Shape of the kWaveGrid 

41 

42 Returns: 

43 None 

44 """ 

45 # check the absorption mode input is valid 

46 if self.alpha_mode is not None: 

47 assert self.alpha_mode in ['no_absorption', 'no_dispersion', 'stokes'], \ 

48 "medium.alpha_mode must be set to 'no_absorption', 'no_dispersion', or 'stokes'." 

49 

50 # check the absorption filter input is valid 

51 if self.alpha_filter is not None and not (self.alpha_filter.shape == kgrid_shape).all(): 

52 raise ValueError('medium.alpha_filter must be the same size as the computational grid.') 

53 

54 # check the absorption sign input is valid 

55 if self.alpha_sign is not None and (not kwave.utils.checkutils.is_number(self.alpha_sign) or (self.alpha_sign.size != 2)): 

56 raise ValueError('medium.alpha_sign must be given as a 2 element numerical array controlling absorption and dispersion, respectively.') 

57 

58 # check alpha_coeff is non-negative and real 

59 if not np.all(np.isreal(self.alpha_coeff)) or np.any(self.alpha_coeff < 0): 

60 raise ValueError('medium.alpha_coeff must be non-negative and real.') 

61 

62 def is_defined(self, *fields) -> List[bool]: 

63 """ 

64 Check if the field(s) are defined or None 

65 

66 Args: 

67 *fields: String list of the fields 

68 

69 Returns: 

70 Boolean list 

71 """ 

72 results = [] 

73 for f in fields: 

74 results.append(getattr(self, f) is not None) 

75 return results 

76 

77 def ensure_defined(self, *fields) -> None: 

78 """ 

79 Assert that the field(s) are defined (not None) 

80 

81 Args: 

82 *fields: String list of the fields 

83 

84 Returns: 

85 None 

86 """ 

87 for f in fields: 

88 assert getattr(self, f) is not None, f'The field {f} must be not be None' 

89 

90 def is_nonlinear(self) -> bool: 

91 """ 

92 Check if the medium is nonlinear 

93 

94 Returns: 

95 whether the fluid simulation is nonlinear 

96 """ 

97 return self.BonA is not None 

98 

99 def set_absorbing(self, is_absorbing, is_stokes=False) -> None: 

100 """ 

101 Change medium's absorbing and stokes properties 

102 

103 Args: 

104 is_absorbing: Is the medium absorbing 

105 is_stokes: Is the medium stokes 

106 Returns: 

107 None 

108 """ 

109 # only stokes absorption is supported in the axisymmetric code 

110 self.absorbing, self.stokes = is_absorbing, is_stokes 

111 if is_absorbing: 

112 if is_stokes: 

113 self._check_absorbing_with_stokes() 

114 else: 

115 self._check_absorbing_without_stokes() 

116 

117 def _check_absorbing_without_stokes(self) -> None: 

118 """ 

119 Check if the medium properties are set correctly for absorbing simulation without stokes 

120 

121 Returns: 

122 None 

123 """ 

124 # enforce both absorption parameters 

125 self.ensure_defined('alpha_coeff', 'alpha_power') 

126 

127 # check y is a scalar 

128 assert np.isscalar(self.alpha_power), 'medium.alpha_power must be scalar.' 

129 

130 # check y is real and within 0 to 3 

131 assert np.all(np.isreal(self.alpha_coeff)) and 0 < self.alpha_power < 3, \ 

132 'medium.alpha_power must be a real number between 0 and 3.' 

133 

134 # display warning if y is close to 1 and the dispersion term has not been set to zero 

135 if self.alpha_mode != 'no_dispersion': 

136 assert self.alpha_power != 1, \ 

137 """The power law dispersion term in the equation of state is not valid for medium.alpha_power = 1. 

138 This error can be avoided by choosing a power law exponent close to, but not exactly, 1. 

139 If modelling acoustic absorption for medium.alpha_power = 1 is important and modelling dispersion is not 

140 critical, this error can also be avoided by setting medium.alpha_mode to 'no_dispersion'""" 

141 

142 def _check_absorbing_with_stokes(self): 

143 """ 

144 Check if the medium properties are set correctly for absorbing simulation with stokes 

145 

146 Returns: 

147 None 

148 """ 

149 # enforce absorption coefficient 

150 self.ensure_defined('alpha_coeff') 

151 

152 # give warning if y is specified 

153 if self.alpha_power is not None and (self.alpha_power.size != 1 or self.alpha_power != 2): 

154 warn('WARNING: the axisymmetric code and stokes absorption assume alpha_power = 2, user value ignored.') 

155 

156 # overwrite y value 

157 self.alpha_power = 2 

158 

159 # don't allow medium.alpha_mode with the axisymmetric code 

160 if self.alpha_mode is not None and (self.alpha_mode in ['no_absorption', 'no_dispersion']): 

161 raise NotImplementedError('Input option medium.alpha_mode is not supported with the axisymmetric code ' 

162 'or medium.alpha_mode = ''stokes''.') 

163 

164 # don't allow alpha_filter with stokes absorption (no variables are applied in k-space) 

165 assert self.alpha_filter is None, \ 

166 "Input option medium.alpha_filter is not supported with the axisymmetric code " \ 

167 "or medium.alpha_mode = 'stokes'. " 

168 

169 ########################################## 

170 # Elastic-code related properties - raise error when accessed 

171 ########################################## 

172 _ELASTIC_CODE_ACCESS_ERROR_TEXT_ = 'Elastic simulation and related properties are not supported!' 

173 

174 @property 

175 def sound_speed_shear(self): # pragma: no cover 

176 """ 

177 Shear sound speed (used in elastic simulations | not supported currently!) 

178 """ 

179 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_) 

180 

181 @property 

182 def sound_speed_ref_shear(self): # pragma: no cover 

183 """ 

184 Shear sound speed reference (used in elastic simulations | not supported currently!) 

185 """ 

186 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_) 

187 

188 @property 

189 def sound_speed_compression(self): # pragma: no cover 

190 """ 

191 Compression sound speed (used in elastic simulations | not supported currently!) 

192 """ 

193 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_) 

194 

195 @property 

196 def sound_speed_ref_compression(self): # pragma: no cover 

197 """ 

198 Compression sound speed reference (used in elastic simulations | not supported currently!) 

199 """ 

200 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_) 

201 

202 @property 

203 def alpha_coeff_compression(self): # pragma: no cover 

204 """ 

205 Compression alpha coefficient (used in elastic simulations | not supported currently!) 

206 """ 

207 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_) 

208 

209 @property 

210 def alpha_coeff_shear(self): # pragma: no cover 

211 """ 

212 Shear alpha coefficient (used in elastic simulations | not supported currently!) 

213 """ 

214 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_)