Coverage for kwave/kmedium.py: 34%

60 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-03-24 12:06 -0700

1import logging 

2from dataclasses import dataclass 

3from typing import List 

4 

5import numpy as np 

6 

7import kwave.utils.checks 

8 

9 

10@dataclass 

11class kWaveMedium(object): 

12 # sound speed distribution within the acoustic medium [m/s] | required to be defined 

13 sound_speed: np.array 

14 # reference sound speed used within the k-space operator (phase correction term) [m/s] 

15 sound_speed_ref: np.array = None 

16 # density distribution within the acoustic medium [kg/m^3] 

17 density: np.array = None 

18 # power law absorption coefficient [dB/(MHz^y cm)] 

19 alpha_coeff: np.array = None 

20 # power law absorption exponent 

21 alpha_power: np.array = None 

22 # optional input to force either the absorption or dispersion terms in the equation of state to be excluded; 

23 # valid inputs are 'no_absorption' or 'no_dispersion' 

24 alpha_mode: np.array = None 

25 # frequency domain filter applied to the absorption and dispersion terms in the equation of state 

26 alpha_filter: np.array = None 

27 # two element array used to control the sign of absorption and dispersion terms in the equation of state 

28 alpha_sign: np.array = None 

29 # parameter of nonlinearity 

30 BonA: np.array = None 

31 # is the medium absorbing? 

32 absorbing: bool = False 

33 # is the medium absorbing stokes? 

34 stokes: bool = False 

35 

36 # """ 

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

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

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

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

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

42 # """ 

43 

44 def __post_init__(self): 

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

46 

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

48 """ 

49 Check whether the given properties are valid 

50 

51 Args: 

52 kgrid_shape: Shape of the kWaveGrid 

53 

54 Returns: 

55 None 

56 """ 

57 # check the absorption mode input is valid 

58 if self.alpha_mode is not None: 

59 assert self.alpha_mode in [ 

60 "no_absorption", 

61 "no_dispersion", 

62 "stokes", 

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

64 

65 # check the absorption filter input is valid 

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

67 raise ValueError("medium.alpha_filter must be the same size as the computational grid.") 

68 

69 # check the absorption sign input is valid 

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

71 raise ValueError( 

72 "medium.alpha_sign must be given as a " "2 element numerical array controlling absorption and dispersion, respectively." 

73 ) 

74 

75 # check alpha_coeff is non-negative and real 

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

77 raise ValueError("medium.alpha_coeff must be non-negative and real.") 

78 

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

80 """ 

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

82 

83 Args: 

84 *fields: String list of the fields 

85 

86 Returns: 

87 Boolean list 

88 """ 

89 results = [] 

90 for f in fields: 

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

92 return results 

93 

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

95 """ 

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

97 

98 Args: 

99 *fields: String list of the fields 

100 

101 Returns: 

102 None 

103 """ 

104 for f in fields: 

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

106 

107 def is_nonlinear(self) -> bool: 

108 """ 

109 Check if the medium is nonlinear 

110 

111 Returns: 

112 whether the fluid simulation is nonlinear 

113 """ 

114 return self.BonA is not None 

115 

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

117 """ 

118 Change medium's absorbing and stokes properties 

119 

120 Args: 

121 is_absorbing: Is the medium absorbing 

122 is_stokes: Is the medium stokes 

123 Returns: 

124 None 

125 """ 

126 # only stokes absorption is supported in the axisymmetric code 

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

128 if is_absorbing: 

129 if is_stokes: 

130 self._check_absorbing_with_stokes() 

131 else: 

132 self._check_absorbing_without_stokes() 

133 

134 def _check_absorbing_without_stokes(self) -> None: 

135 """ 

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

137 

138 Returns: 

139 None 

140 """ 

141 # enforce both absorption parameters 

142 self.ensure_defined("alpha_coeff", "alpha_power") 

143 

144 # check y is a scalar 

145 assert np.isscalar(self.alpha_power), "medium.alpha_power must be scalar." 

146 

147 # check y is real and within 0 to 3 

148 assert ( 

149 np.all(np.isreal(self.alpha_coeff)) and 0 <= self.alpha_power < 3 

150 ), "medium.alpha_power must be a real number between 0 and 3." 

151 

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

153 if self.alpha_mode != "no_dispersion": 

154 assert self.alpha_power != 1, """The power law dispersion term in the equation of state is not valid for medium.alpha_power = 1. 

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

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

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

158 

159 def _check_absorbing_with_stokes(self): 

160 """ 

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

162 

163 Returns: 

164 None 

165 """ 

166 # enforce absorption coefficient 

167 self.ensure_defined("alpha_coeff") 

168 

169 # give warning if y is specified 

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

171 logging.log(logging.WARN, "the axisymmetric code and stokes absorption assume alpha_power = 2, user value ignored.") 

172 

173 # overwrite y value 

174 self.alpha_power = 2 

175 

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

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

178 raise NotImplementedError( 

179 "Input option medium.alpha_mode is not supported with the axisymmetric code " "or medium.alpha_mode = " "stokes" "." 

180 ) 

181 

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

183 assert self.alpha_filter is None, ( 

184 "Input option medium.alpha_filter is not supported with the axisymmetric code " "or medium.alpha_mode = 'stokes'. " 

185 ) 

186 

187 ########################################## 

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

189 ########################################## 

190 _ELASTIC_CODE_ACCESS_ERROR_TEXT_ = "Elastic simulation and related properties are not supported!" 

191 

192 @property 

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

194 """ 

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

196 """ 

197 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_) 

198 

199 @property 

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

201 """ 

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

203 """ 

204 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_) 

205 

206 @property 

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

208 """ 

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

210 """ 

211 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_) 

212 

213 @property 

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

215 """ 

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

217 """ 

218 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_) 

219 

220 @property 

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

222 """ 

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

224 """ 

225 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_) 

226 

227 @property 

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

229 """ 

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

231 """ 

232 raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_)