Coverage for kwave/kmedium.py: 51%

1 from dataclasses import dataclass

2 from typing import List

3 from warnings import warn

4

5 import numpy as np

6 import kwave.utils.misc as util

7 import kwave.utils.checkutils

8

9

10@dataclass

11 class 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_)